Как заставить 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');
Другие вопросы по тегам