Я написал простой основанный на 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
Но есть как минимум две проблемы с этим кодом:
Мне нужно сохранить список всех известных реализаций в _build_fetcher
.Я бы предпочел, чтобы код автоматически выбирал из каждого загруженного модуля/класса в пространстве имен Document::Fetcher::
. Или, может быть, есть лучший способ «зарегистрировать» такие плагины?
На данный момент весь код выглядит слишком раздутым. Я уверен, что люди уже писали такую систему плагинов. Разве в MooseXнет чего-то, что обеспечивает желаемое поведение?