He escrito una clase simple Moose llamada Document
. Esta clase tiene dos atributos: name
y homepage
.Perl/Moose: ¿cómo puedo elegir dinámicamente una implementación específica de un método?
La clase también necesita proporcionar un método llamado do_something()
que recupera y devuelve texto de diferentes fuentes (como un sitio web o bases de datos diferentes) basado en el atributo homepage
.
Dado que habrá muchas implementaciones totalmente diferentes para do_something()
, me gustaría tenerlas en diferentes paquetes/clases y cada una de estas clases debería saber si es responsable del atributo homepage
o si no lo está .
Mi enfoque implica hasta ahora dos funciones:
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';
una clase llamada Document::Fetcher
que proporciona una implmenentation predeterminado para do_something()
y métodos de uso común (como una solicitud GET HTTP):
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;
# ...
}
Y las implementaciones específicas que determinan su responsabilidad a través de un método llamado 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#; }
Mi clase Document
se ve así:
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);
}
En este momento esto funciona como debería. Si llamo el siguiente código:
foreach my $i (qw/foo bar baz/) {
my $doc = Document->new(name => $i, homepage => "http://$i.tld/");
say $doc->name . ": " . $doc->do_something;
}
consigo la salida esperada:
foo: called from implementation A
bar: called from implementation B
baz: called from default implementation
Pero hay al menos dos problemas con este código:
necesito mantener una lista de todas las implementaciones conocidas en
_build_fetcher
. Prefiero una forma en la que el código elija automáticamente de cada módulo/clase cargado debajo del espacio de nombresDocument::Fetcher::
. ¿O tal vez hay una mejor manera de "registrar" este tipo de complementos?Por el momento todo el código parece un poco demasiado hinchado. Estoy seguro de que la gente ha escrito este tipo de sistema de complementos antes. ¿No hay algo en MooseX que proporciona el comportamiento deseado?
Ni siquiera he pensado en principios como GRASP. De alguna manera, la forma en que lo hice parecía ser "una buena manera" con Moose. Ahora que los has mencionado, por supuesto tiene sentido para usar una fábrica abstracta. Todavía no estoy seguro de cómo registrar cada función. ¿Esto no requeriría algún tipo de clase de Singleton? En este momento estoy usando una solución algo hacky: examinando '% Document :: Fetcher ::'. –
@SebastianStumpf No tiene que complicarse las cosas solo porque la gente de Moose tiene un resentimiento filosófico contra los datos de clase, ni tiene que alcanzar las variables globales. La encapsulación normal todavía funciona. – Schwern
Resolví esto al final un poco más "Mooseish" al agregar un rasgo a la clase meta de la factoría que tiene un atributo 'ArrayRef [Str]' llamado 'fetchers'. Así que puedo simplemente llamar a '__PACKAGE __-> meta-> fetchers-> add'. :-) –