我已經寫了叫Document
簡單Moose基於類。這門課有兩個屬性:name
和homepage
。Perl/Moose - 如何動態選擇某個方法的特定實現?
該類還需要提供一種名爲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';
稱爲Document::Fetcher
一類,它提供一個默認的implmenentation爲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;
# ...
}
並決定通過一個方法爲己任的具體實現所謂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#; }
我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中沒有提供所需行爲的東西嗎?
我甚至沒有想過像GRASP這樣的原理。不知怎的,我做到這一點似乎是與穆斯「好方法」。現在您已經提到過,使用抽象工廠當然非常合理。我仍然不確定如何註冊每個角色。這不需要某種Singleton類嗎?現在我正在使用一個有點冒險的解決方案:檢查'%Document :: Fetcher ::'。 –
@SebastianStumpf你不必因爲穆斯的人對班級數據有哲學怨恨而使事情變得複雜,也不必爲全局變量伸手。正常封裝仍然有效。 – Schwern
我通過在factorie的元類中添加一個特徵來解決這個問題,該特徵類包含一個名爲'fetchers'的'ArrayRef [Str]'屬性。所以我可以調用'__PACKAGE __-> meta-> fetchers-> add'。 :-) –