2009-08-07 9 views
11

¿Existe alguna manera más simple o mejor (= > más fácil de mantener) de usar Perl y Moose para crear instancias de clases basadas en datos entrantes?¿Cómo puedo codificar una fábrica en Perl y Moose?

El siguiente código es una muestra reducida de un proyecto en el que estoy trabajando.

package FooBar; 
use Moose; 
has 'SUBCLASS' =>('isa'=>'Str',required=>'1',is=>'ro'); 
has 'MSG' =>('isa'=>'Str',required=>'1',is=>'ro'); 

sub BUILD { 
     my $self = shift; 
     my ($a)[email protected]_; 
     bless($self,$a->{SUBCLASS}) 
} 
sub Hi { 
    my $self=shift; 
    print "Hi, I'm a " . ref($self) ." and I say [". $self->MSG()."]\n"; 
} 

package Foo; 
use Moose; 
extends ("FooBar"); 

package Bar; 
use Moose; 
extends ("FooBar"); 

package main; 
use strict; 
use warnings; 

for my $line (<DATA>) { 
    my ($case,$msg)=split(/[\n\r,]\s*/,$line); 
    FooBar->new(SUBCLASS=>$case,MSG=>$msg)->Hi(); 
} 

__DATA__ 
Foo, First Case 
Bar, Second Case 

EDITAR: Simplemente me ocurrió que esto es más o menos lo que sucede cuando se llama a la DBI. En función de los parámetros que lo pasa, utilizará el código completamente diferente, manteniendo a (sobre todo) interfaz consistente

Respuesta

10

Ick. Stevan tiene un argumento muy convincente de que new siempre debería solo devolver una instancia de Clase. Cualquier otra cosa es confusa para las personas nuevas que están aprendiendo el sistema.

Es posible que desee echar un vistazo a MooseX::AbstractFactory. Si esto no funciona para usted, entonces:

package FooBar; 
use Moose; 

has [qw(SUBCLASS MSG)] => (is => 'ro', required => 1); 

sub create_instance { 
    return $self->package->new(message => $self->msg); 
} 

package FooBar::Object; 
use Moose; 

has msg => (is => 'ro', required => 1); 

sub Hi { 
    my $self = shift; 
    print "Hi, I'm a " . ref($self) ." and I say [". $self->MSG()."]\n"; 
} 

package Foo; 
use Moose; 
extends qw(FooBar::Object); 

package Bar; 
use Moose; 
extends qw(FooBar::Object); 


package main; 
or my $line (<DATA>) { 
    my ($case,$msg)=split(/[\n\r,]\s*/,$line); 
    FooBar->new(SUBCLASS=>$case,MSG=>$msg)->create_instance->Hi 
} 

__DATA__ 
Foo, First Case 
Bar, Second Case 

Por supuesto hay muchas otras maneras de implementar este mismo concepto en Moose. Sin conocer los detalles de su problema de dominio que es difícil decir que algo como MooseX::Traits no sería mejor:

package Foo; 
use Moose; 
with qw(MooseX::Traits); 

package Bar; 
use Moose; 
with qw(MooseX::Traits); 

package Messaging; 
use Moose::Role; 

has msg => (is => 'ro', required => 1); 

sub Hi { 
    my $self = shift; 
    print "Hi, I'm a " . ref($self) ." and I say [". $self->MSG()."]\n"; 
} 

package main; 
use strict; 
Foo->with_traits('Messaging')->new(msg => 'First Case')->Hi; 

Esto es aproximadamente lo que el otro cartel significaba sobre el uso de una solución basada en roles.

+0

gracias, me tomará un tiempo para grok :-) – lexu

+1

** tiene [qw (SUBCLASE MSG)] => (es => 'ro', requerido => 1); ** buen truco ... pero "poco intuitivo" para cualquiera que no esté familiarizado con perl .. – lexu

+0

No, no lo es. Es parte de la API de Moose y no tiene nada que ver con Perl. (Si quisiera ser poco intuitivo, escribiría "tiene $ _ => (...) para qw/SUBCLASS MSG /". Pero, por supuesto, todos saben lo que hace esto también.) – jrockway

5

Usted podría simplemente hacer:

$case->new(MSG => $msg)->Hi(); 

Si eso es más fácil o mejor depende de usted para decidir.

+0

usando una variable como la clase a instanciar ** $ case-> new (..) ** de esa manera se siente muy extraño. Pero sí, extremadamente compacto, en comparación con mi código (donde lo deletreo ** bless ({}, $ case) ** Gracias por el consejo! – lexu

+1

¿Por qué es extraño? Un nombre de clase en Perl es solo una string. Foo-> blah es exactamente lo mismo que "Foo" -> bla. –

+1

Si no hay lógica en la fábrica para elegir la subclase correcta para instanciar, entonces no es una fábrica y esto es por un amplio margen la mejor solución . – Schwern

4

Pues bien, el objeto ya se crea cuando BUILD se llama, así que diría

sub BUILD { 
     my $self = shift; 
     return bless $self, $self->SUBCLASS; 
} 

Siempre se puede desear cambiar de un modelo basado en la herencia de un modelo basado en roles donde se crea el objeto que desea (en lugar de pasar la clase a la clase de fábrica), luego aplique el rol común.

+0

Donde ¿Me enviarías a aprender/entender la transición de "herencia basada" a "basada en roles"? – lexu

+3

El manual de Moose sobre roles sería un comienzo http://search.cpan.org/perldoc/Moose::Manual::Roles –

+0

@Sinan Ünür: gracias por ese enlace! – lexu

5

Sólo una nota sobre algunas de las respuestas:

Calling bless en BUILD, o en cualquier lugar fuera de los componentes internos del MOP, es siempre inaceptable. (¡Si debe utilizar la tarjeta de memoria, hay Class::MOP::Class->rebless_instance!)

En segundo lugar, aconsejo no permitir que new devuelva nada que no sea una instancia de __PACKAGE__. Si desea un método que cree una instancia de algo, llámelo de otra forma.Ejemplo:

class Message { 
    method new_from_string(Str $msg){ 
     my ($foo, $bar, $baz) = ($msg =~ /<...>/); # blah blah blah 
     my $class = "Message::${foo}::$baz"; 
     Class::MOP::load_class($class); 
     return $class->new(bar => $msg); 
    } 
} 

Entonces, cuando se quiere crear un mensaje literal:

Message->new(whatever => 'you want'); 

Cuando se quiere analizar una cadena y devolver el mensaje correcto subclase:

Message->new_from_string('OH::Hello!'); 

Por último, si no tiene sentido poder crear una instancia de Mensaje, entonces no debería ser una clase. Debería ser un rol.

Puede manejar la construcción con algún otro objeto, por supuesto. Sólo asegúrese de que este otro objeto es responsable sólo de entender el formato de cadena, por ejemplo, y no internos mensaje:

class MessageString { 
    has 'string' => (initarg => 'string', reader => 'message_as_string'); 

    method new_from_string(ClassName $class: Str $string) { 
     return $class->new(string => $string); 
    } 

    method as_message_object { 
     # <parse> 
     return Message::Type->new(params => 'go here', ...); 
    } 
} 

role Message { ... } 
class Message::Type with Message { ... } 

Ahora usted ya no está preocupado por tener un poco de "superclase" responsable de la construcción "subclases", el cual Creo que es un mejor diseño. (Recuerde, messageString tiene ningún poder especial sobre las clases que hacen "mensaje" Esa es la clave aquí, es sólo responsable de entender los mensajes Stringified..)

De todos modos, ahora sólo:

my $data = <>; # Yup, I called it $data. Sorry, Andy Lester. 
my $parsed = MessageString->new_from_string($data); 
my $message = $parsed->as_message_object; 
$message->interact_with 

(Usted sabe "MVC"? Esto es similar.)

3

Simplemente use otro objeto de fábrica para construir objetos de esa clase.

más simple, más flexible, más fiable, etc.

my $factory = Factory->new(... factory parameters ...);

my $object = $factory->new_object(... various parameters ...);

donde new_object puede analizar los parámetros y tomar decisiones tanto en los datos dentro $factory y los datos de esos parámetros.

Cuando descubra que necesitará objetos codependientes en el próximo paso, busque un marco de inversión de control.

Cuestiones relacionadas