Как я могу создать объект, чей производный класс неявно указан в свойствах создания?
-
05-07-2019 - |
Вопрос
Я ищу шаблон для следующего. (Я работаю в Perl, но не думаю, что язык имеет особое значение).
С родительским классом Foo и детьми Bar, Baz, Bazza. Р>
Один из методов создания Foo - это анализ строки, и часть этой строки будет неявно указывать, какой класс должен быть создан. Так, например, если он запускает «http:», то это Bar, но если нет, но содержит «[Date]», то Baz это нравится, и так далее.
Теперь, если Foo знает обо всех своих дочерних элементах, а также о том, какая строка является Bar, что такое Baz и т. д., он может вызвать соответствующий конструктор. Но базовый класс не должен иметь никаких знаний о своих детях.
Я хочу, чтобы конструктор Foo мог по очереди проверять своих потомков, пока один из них не скажет "Да, это мое, я создам эту вещь". Р>
Я понимаю, что в общем случае эта проблема не является четко определенной, поскольку может быть несколько дочерних элементов, которые примут строку, и поэтому порядок, в котором они называются, имеет значение: игнорируйте это и предполагайте, что характеристики строки таковы, что строка может понравиться только одному дочернему классу.
Лучшее, что я придумал, - это чтобы дочерние классы «регистрировались» в базовом классе при инициализации, чтобы он получал список конструкторов и затем проходил через них. Но есть ли лучший метод, который мне не хватает?
Пример кода:
package Foo;
my @children;
sub _registerChild
{
push @children, shift();
}
sub newFromString
{
my $string = shift;
foreach (@children) {
my $object = Я ищу шаблон для следующего. (Я работаю в Perl, но не думаю, что язык имеет особое значение).
С родительским классом Foo и детьми Bar, Baz, Bazza. Р>
Один из методов создания Foo - это анализ строки, и часть этой строки будет неявно указывать, какой класс должен быть создан. Так, например, если он запускает «http:», то это Bar, но если нет, но содержит «[Date]», то Baz это нравится, и так далее.
Теперь, если Foo знает обо всех своих дочерних элементах, а также о том, какая строка является Bar, что такое Baz и т. д., он может вызвать соответствующий конструктор. Но базовый класс не должен иметь никаких знаний о своих детях.
Я хочу, чтобы конструктор Foo мог по очереди проверять своих потомков, пока один из них не скажет "Да, это мое, я создам эту вещь". Р>
Я понимаю, что в общем случае эта проблема не является четко определенной, поскольку может быть несколько дочерних элементов, которые примут строку, и поэтому порядок, в котором они называются, имеет значение: игнорируйте это и предполагайте, что характеристики строки таковы, что строка может понравиться только одному дочернему классу.
Лучшее, что я придумал, - это чтобы дочерние классы «регистрировались» в базовом классе при инициализации, чтобы он получал список конструкторов и затем проходил через них. Но есть ли лучший метод, который мне не хватает?
Пример кода:
<*>->newFromString(@_) and return $object;
}
return undef;
}
package Bar;
our @ISA = ('Foo');
Foo::_registerChild(__PACKAGE__);
sub newFromString
{
my $string = shift;
if ($string =~ /^http:/i) {
return bless(...);
}
return undef;
}
Решение
Возможно, вы могли бы реализовать это с помощью модуля: : вставные ? Это устранит необходимость в регистрации. Р>
Подход, который я использовал ранее, заключался в использовании Module :: Pluggable для загрузки моих дочерних модулей (это позволило мне добавить новые дочерние модули, просто написав и установив их). Каждый из дочерних классов будет иметь конструктор, который либо возвращает благословенный объект, либо undef. Вы перебираете свои плагины, пока не получите объект, а затем возвращаете его.
Что-то вроде:
package MyClass;
use Module::Pluggable;
sub new
{
my ($class, @args) = @_;
for my $plugin ($class->plugins)
{
my $object = $plugin->new(@args);
return $object if $object;
}
}
Существует класс: фабрика также, но это может быть немного больше для ваших нужд.
Другие советы
Кажется, вы пытаетесь, чтобы один класс был и базовым классом, и фабрикой. Не. Используйте 2 отдельных класса. Примерно так:
package Foo;
package Bar;
use base 'Foo';
package Baz;
use base 'Foo';
package Bazza;
use base 'Foo';
package Factory;
use Bar;
use Baz;
use Bazza;
sub get_foo {
my ($class, $string) = @_;
return Bar->try($string) || Baz->try($string) || Bazza->try($string);
}
А затем используйте его следующим образом:
my $foo = Factory->get_foo($string);
Таким образом, ваш базовый класс не должен знать о ваших дочерних классах, а только ваша фабрика. И дочерние классы также не должны знать друг о друге, только Фабрика должна знать детали, какие дочерние классы пробовать и в каком порядке.
Вы можете реализовать произвольный алгоритм поиска в классе Foo, который ищет существующие дочерние классы. Может быть, основано на конфигурационных файлах, предоставленных дочерними классами, или на любом другом механизме, о котором вы можете подумать.
Затем класс Foo обнаружит существующие клиентские классы во время выполнения и вызовет их по очереди.
Кроме того, вы можете кэшировать результаты поиска и приблизиться к решению реестра, которое вы уже описали сами.
Если вы возьмете свой комментарий о том, что родительский класс не содержит информации о детях, и о том, как вы делегировали задачу по установлению соответствия дочерних классов самому классу, то, вероятно, будет правильным выделение выбора класса из родительского класса. и создайте синглтон для этой задачи.
по крайней мере, это было бы моим предпочтением ... из-за этого ваш текущий родительский класс (который, вероятно, имеет некоторые общие функциональные возможности в ваших дочерних классах), по-видимому, может затем стать абстрактным или интерфейсом.
Синглтон может затем управлять созданием всех дочерних классов и их распределением (клонировать их, если они не работают?) ... кроме того, дочерние классы могут быть перемещены в отдельную dll для обеспечения разделения.
извините, это не прямое решение. Я делал это в прошлом, управляя списком классов в синглтоне так же, как вы здесь. идея синглтона заключается в том, что если вы хотите использовать какое-либо дорогое отражение, вам нужно сделать это только один раз. Р>