Perl/Moose. Как я могу динамически выбирать конкретную реализацию метода?

Я написал простой основанный на Mooseкласс с именем Document. Этот класс имеет два атрибута: nameи homepage.

Класс также должен предоставить метод с именем do_something(), который извлекает и возвращает текст из разных источников (таких как веб-сайт или разные базы данных) на основе атрибута домашней страницы.

Поскольку будет много совершенно разных реализаций для do_something(), я хотел бы иметь их в разных пакетах/классах, и каждый из этих классов должен знать, отвечает ли он за домашняя страницаатрибут или если это не так.

Пока мой подход включает две роли:

package Role::Fetcher;
use Moose::Role;
requires 'do_something';
has url => (
    is => 'ro',
    isa => 'Str'
);

package Role::Implementation;
use Moose::Role;
with 'Role::Fetcher';
requires 'responsible';

Класс с именем Document::Fetcher, который обеспечивает реализацию по умолчанию для do_something()и часто используемые методы (такие как запрос HTTP GET):

package Document::Fetcher;
use Moose;
use LWP::UserAgent;
with 'Role::Fetcher';

has ua => (
    is => 'ro',
    isa => 'Object',
    required => 1,
    default => sub { LWP::UserAgent->new }
);

sub do_something {'called from default implementation'}
sub get {
    my $r = shift->ua->get(shift);
    return $r->content if $r->is_success;
    # ...
}

И конкретные реализации, которые определяют их ответственность с помощью метода, называемого ответственным():

package Document::Fetcher::ImplA;
use Moose;
extends 'Document::Fetcher';
with 'Role::Implementation';

sub do_something {'called from implementation A'}
sub responsible { return 1 if shift->url =~ m#foo#; }

package Document::Fetcher::ImplB;
use Moose;
extends 'Document::Fetcher';
with 'Role::Implementation';

sub do_something {'called from implementation B'}
sub responsible { return 1 if shift->url =~ m#bar#; }

Мой класс Documentвыглядит следующим образом:

package Document;
use Moose;

has [qw/name homepage/] => (
    is => 'rw',
    isa => 'Str'
);

has fetcher => (
    is => 'ro',
    isa => 'Document::Fetcher',
    required => 1,
    lazy => 1,
    builder => '_build_fetcher',
    handles => [qw/do_something/]
);

sub _build_fetcher {
    my $self = shift;
    my @implementations = qw/ImplA ImplB/;

    foreach my $i (@implementations) {
        my $fetcher = "Document::Fetcher::$i"->new(url => $self->homepage);
        return $fetcher if $fetcher->responsible();
    }

    return Document::Fetcher->new(url => $self->homepage);
}

Прямо сейчас это работает так, как должно. Если я вызову следующий код:

foreach my $i (qw/foo bar baz/) {
    my $doc = Document->new(name => $i, homepage => "http://$i.tld/");
    say $doc->name . ": " . $doc->do_something;
}

Я получу ожидаемый результат:

foo: called from implementation A
bar: called from implementation B
baz: called from default implementation

Но есть как минимум две проблемы с этим кодом:

  1. Мне нужно сохранить список всех известных реализаций в _build_fetcher.Я бы предпочел, чтобы код автоматически выбирал из каждого загруженного модуля/класса в пространстве имен Document::Fetcher::. Или, может быть, есть лучший способ «зарегистрировать» такие плагины?

  2. На данный момент весь код выглядит слишком раздутым. Я уверен, что люди уже писали такую ​​систему плагинов. Разве в MooseXнет чего-то, что обеспечивает желаемое поведение?

6
задан Sebastian Stumpf 8 June 2012 в 19:16
поделиться