I've written a simple Moose based class called Document
. This class has two attributes: name
and homepage
.
The class also needs to provide a method called do_something()
which retrieves and returns text from different sources (like a website or different databases) based on the homepage
attribute.
Since there will be a lot of totally different implementations for do_something()
, I'd like to have them in different packages/classes and each of these classes should know if it is responsible for the homepage
attribute or if it isn't.
My approach so far involves two roles:
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';
A class called Document::Fetcher
which provides a default implmenentation for do_something()
and commonly used methods (like a HTTP GET request):
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;
# ...
}
And specific implementations which determine their responsibility via a method called 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#; }
My Document
class looks like this:
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);
}
Right now this works as it should. If I call the following code:
foreach my $i (qw/foo bar baz/) {
my $doc = Document->new(name => $i, homepage => "http://$i.tld/");
say $doc->name . ": " . $doc->do_something;
}
I get the expected output:
foo: called from implementation A
bar: called from implementation B
baz: called from default implementation
But there are at least two issues with this code:
I need to keep a list of all known implementations in
_build_fetcher
. I'd prefer a way where the code would automatically choose from every loaded module/class beneath the namespaceDocument::Fetcher::
. Or maybe there's a better way to "register" these kind of plugins?At the moment the whole code looks a bit too bloated. I am sure people have written this kind of plugin system before. Isn't there something in MooseX which provides the desired behaviour?
%Document::Fetcher::
. – Wickerwork