Как заставить Moose возвращать экземпляр дочернего класса вместо своего собственного класса для полиморфизма
Я хочу создать универсальный класс, конструктор которого не будет возвращать экземпляр этого универсального класса, а экземпляр выделенного дочернего класса.
Поскольку Moose выполняет автоматическое построение объектов, я не понимаю, возможно ли это, и как создать класс Moose с синтаксисом Moose и его поведение.
Например: пользователь спрашивает: $file = Repository->new(uri=>'sftp://blabla')
.... и возвращается экземпляр `Repository::_Sftp``
Пользователь будет использовать $file
как будто это экземпляр Repository, без необходимости знать реальный подкласс (полиморфизм)
Замечания:
В соответствии с просьбой, возможно, мне следовало быть более ясным о том, чего я пытался достичь:
Цель моего класса - иметь возможность добавлять новые схемы репозитория (например, через sftp), просто создавая "скрытый" класс Repository::_Stfp и добавив регистр в конструкторе Repository к фабрике правильного специализированного объекта в зависимости от URL-адреса., Репозиторий будет похож на виртуальный базовый класс, предоставляющий интерфейс, который будут реализовывать специализированные объекты.
Все это для добавления новых схем репозитория без изменения остальной части программы: он будет неосознанно иметь дело со специализированным экземпляром, как если бы он был экземпляром репозитория.
2 ответа
new
строит застройщика. Вы хотите, чтобы какой-то другой метод действительно возвращал построенный объект.
Вот пример:
class RepositoryBuilder {
has 'allow_network_repositories' => (
is => 'ro',
isa => 'Bool',
required => 1,
);
method build_repository(Uri $url) {
confess 'network access is not allowed'
if $url->is_network_url && !$self->allow_network_repositories;
my $class = $self->determine_class_for($url); # Repository::Whatever
return $class->new( url => $url );
}
}
role Repository { <whatever }
class Repository::File with Repository {}
class Repository::HTTP with Repository {}
Здесь застройщик и построенный объект различны. Конструктор - это реальный объект с параметрами, которые можно настраивать для построения объектов в зависимости от ситуации. Затем "встроенные" объекты являются просто возвращаемыми значениями метода. Это позволяет строить других строителей в зависимости от ситуации. (Проблема с функциями компоновщика заключается в том, что они очень негибкие - их трудно обучить новому особому случаю. Эта проблема все еще существует с объектом компоновщика, но по крайней мере ваше приложение может создать подкласс, создать его экземпляр и передать этот объект. ко всему, что нужно для создания объектов. Но внедрение зависимостей - лучший подход в этом случае.)
Кроме того, нет необходимости, чтобы репозитории, которые вы строите, наследовали от чего-либо, им просто нужен тег, указывающий, что они являются репозиториями. И это то, что наши Repository
роль делает. (Вы захотите добавить здесь код API и любые методы, которые следует использовать повторно. Но будьте осторожны с принудительным повторным использованием - вы уверены, что все, помеченные ролью Repository, захотят этот код? Если нет, просто поместите код в другой роль и применить это к классам, которые требуют этой функциональности.)
Вот как мы используем созданный нами конструктор. Если, скажем, вы не хотите касаться сети:
my $b = RepositoryBuilder->new( allow_network_repositories => 0 );
$b->build_repository( 'http://google.com/' ); # error
$b->build_repository( 'file:///home/whatever' ); # returns a Repository::Foo
Но если вы делаете:
my $b = RepositoryBuilder->new( allow_network_repositories => 1 );
$b->build_repository( 'http://google.com/' ); # Repository::HTTP
Теперь, когда у вас есть конструктор, который строит объекты так, как вам нравится, вам просто нужно использовать эти объекты в другом коде. Таким образом, последняя часть головоломки относится к "любому" типу объекта Repository в другом коде. Это просто, вы используете does
вместо isa
:
class SomethingThatHasARepository {
has 'repository' => (
is => 'ro',
does => 'Repository',
required => 1,
);
}
И вы сделали.
Нет (не напрямую) В общем в лося зовет CLASS->new
где CLASS - это Moose::Object, который возвращает экземпляр CLASS.
Можете ли вы описать более подробно, чего вы пытаетесь достичь, и почему вы думаете, что это то, что вы хотите? Вы, вероятно, захотите построить фабричный класс - когда вы вызываете метод для него, он вызовет конструктор соответствующего класса и вернет вам этот объект, не заботясь о конкретном типе, который вы возвращаете:
package MyApp::Factory::Repository;
sub getFactory
{
my ($class, %attrs);
# figure out what the caller wants, and decide what type to return
$class ||= 'Repository::_Sftp';
return $class->new(attr1 => 'foo', attr2 => 'bar', %attrs);
}
my $file = MyApp::Factory::Repository->getFactory(uri=>'sftp://blabla');