我写了一个简单的基于Moose的课程Document.这个类有两个属性:name和homepage.
该类还需要提供一种方法do_something(),该方法基于该homepage属性从不同的源(如网站或不同的数据库)检索和返回文本.
由于将有很多完全不同的实现do_something(),我希望将它们放在不同的包/类中,并且每个类都应该知道它是否负责该homepage属性,或者它是否负责.
到目前为止我的方法涉及两个角色:
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';
Run Code Online (Sandbox Code Playgroud)
一个被调用的类Document::Fetcher,它提供了一个默认的implmentntation 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;
# ...
}
Run Code Online (Sandbox Code Playgroud)
以及通过以下方法确定其责任的具体实现responsible():
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#; }
Run Code Online (Sandbox Code Playgroud)
我的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);
}
Run Code Online (Sandbox Code Playgroud)
现在,它可以正常工作.如果我调用以下代码:
foreach my $i (qw/foo bar baz/) {
my $doc = Document->new(name => $i, homepage => "http://$i.tld/");
say $doc->name . ": " . $doc->do_something;
}
Run Code Online (Sandbox Code Playgroud)
我得到了预期的输出:
foo: called from implementation A
bar: called from implementation B
baz: called from default implementation
Run Code Online (Sandbox Code Playgroud)
但是这个代码至少存在两个问题:
我需要保留所有已知实现的列表_build_fetcher.我更喜欢一种方法,代码会自动从命名空间下面的每个加载的模块/类中进行选择Document::Fetcher::.或者也许有更好的方法来"注册"这些插件?
目前整个代码看起来有点过于臃肿.我相信人们之前已经编写过这种插件系统.是不是有什么事在MooseX提供所期望的行为?
您正在寻找的是工厂,特别是抽象工厂.Factory类的构造函数将根据其参数确定要返回的实现.
# Returns Document::Fetcher::ImplA or Document::Fetcher::ImplB or ...
my $fetcher = Document::Fetcher::Factory->new( url => $url );
Run Code Online (Sandbox Code Playgroud)
_build_fetcher将进入逻辑Document::Fetcher::Factory->new.这将Fetchers与您的文档分开.而不是文件知道如何确定它需要哪个Fetcher实现,Fetchers可以自己做.
如果您的优先权是允许人们添加新的Fetchers而不必更改工厂,那么您具有Fetcher角色的基本模式能够通知工厂是否能够处理它.在不利方面,Fetcher :: Factory无法知道多个Fetchers可能对给定的URL有效,而且一个可能比另一个更好.
为了避免在Fetcher :: Factory中硬编码大量Fetcher实现,让每个Fetcher角色在加载时将其自身注册到Fetcher :: Factory.
my %Registered_Classes;
sub register_class {
my $class = shift;
my $registeree = shift;
$Registered_Classes{$registeree}++;
return;
}
sub registered_classes {
return \%Registered_Classes;
}
Run Code Online (Sandbox Code Playgroud)
如果你想要你的蛋糕并且也吃它,你可以拥有一些东西,可能是Document,预加载一堆常见的Fetchers.
| 归档时间: |
|
| 查看次数: |
1225 次 |
| 最近记录: |