Как заставить Moose возвращать экземпляр дочернего класса вместо своего собственного класса для полиморфизма - PullRequest
5 голосов
/ 08 июня 2010

Я хочу создать универсальный класс, конструктор которого не будет возвращать экземпляр этого универсального класса, но экземпляр выделенного дочернего класса.

Поскольку Moose выполняет автоматическое создание объектов, я не могупонять, возможно ли это, и как создать класс Moose с синтаксисом Moose и с таким поведением.

Например: пользователь спрашивает: $file = Repository->new(uri=>'sftp://blabla') .... и ему возвращается `Repository :: _ Sftp``instance

Пользователь будет использовать $file, как если бы это был экземпляр Repository, без необходимости знать реальный подкласс (полиморфизм)

Примечание:
По запросу, возможно, мне следуетбыло более ясно о том, чего я пытался достичь:
Цель моего класса - иметь возможность добавлять новые схемы репозитория (например, через sftp), просто создав «скрытый» класс Repository :: _ Stfp и добавивслучай в конструкторе репозитория для фабрики правильного специализированного объекта в зависимости от URL.Репозиторий будет похож на виртуальный базовый класс, предоставляющий интерфейс, который будут реализовывать специализированные объекты.
Все это для добавления новых схем репозитория без изменения остальной части программы: он будет неосознанно иметь дело со специализированным экземпляром какесли это экземпляр репозитория.

Ответы [ 2 ]

6 голосов
/ 08 июня 2010

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 здесь и любые методы, которые следует использовать повторно. Но будь осторожен о принудительном повторном использовании - вы уверены, что все помечены Роль репозитория захочет этот код? Если нет, просто введите код в другую роль и применить это к классам, которые требуют, чтобы функциональность.) * +1010 *

Вот как мы используем созданный нами конструктор. Если, скажем, вы не хотите коснитесь сети:

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

Теперь, когда у вас есть строитель, который строит объекты так, как вам нравится, вам просто нужно использовать эти объекты в другом коде. Итак, последний кусок в загадке относится к «любому» типу объекта репозитория в другом код. Это просто, вы используете does вместо isa:

class SomethingThatHasARepository {
    has 'repository' => (
       is       => 'ro',
       does     => 'Repository',
       required => 1,
    );
}

И все готово.

2 голосов
/ 08 июня 2010

Нет (не напрямую). В общем случае в Moose вызов CLASS->new, где CLASS - это Moose :: Object, возвращает экземпляр CLASS.

Можете ли вы описать более подробно, чего вы пытаетесь достичь, и почему вы думаете, что это то, что вы хотите? Возможно, вы захотите создать класс factory - когда вы вызываете метод для него, он вызывает конструктор соответствующего класса и возвращает вам этот объект, без необходимости заниматься конкретным типом, который вы получаете назад:

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');
...