Как я могу кодировать фабрику в Perl и Moose? - PullRequest
11 голосов
/ 07 августа 2009

Существует ли более простой или лучший (=> простой в обслуживании) способ использования Perl и Moose для создания экземпляров классов на основе входящих данных?

Следующий код представляет собой упрощенный пример проекта, над которым я работаю.

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)=@_;
      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

РЕДАКТИРОВАТЬ : Меня просто поразило, что именно так и происходит, когда вы вызываете DBI. В зависимости от параметров, которые вы передаете, он будет использовать совершенно другой код при сохранении (в основном) согласованного интерфейса

Ответы [ 5 ]

10 голосов
/ 07 августа 2009

Ик. У Стевана очень убедительный аргумент, что new всегда должен только вернуть экземпляр класса. Все остальное сбивает с толку новых людей, изучающих система.

Возможно, вы захотите взглянуть на MooseX :: AbstractFactory . Если это не сработает, тогда:

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

Конечно, есть много других способов реализовать эту же концепцию в Moose. Не зная специфики проблемы вашего домена, трудно сказать, что что-то вроде MooseX :: Traits не будет лучше:

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;

Это примерно то, что имел в виду другой плакат об использовании решения на основе ролей.

5 голосов
/ 07 августа 2009

Просто отметьте некоторые ответы:

Вызов bless в BUILD или где-либо за пределами MOP всегда недопустим. (Если вы должны rebless, есть Class::MOP::Class->rebless_instance!)

Я рекомендую не разрешать new возвращать что-либо, кроме экземпляра __PACKAGE__. Если вам нужен метод, который создает экземпляр чего-либо, назовите его как-нибудь еще. Пример:

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 );
   }
}

Затем, когда вы хотите создать буквальное сообщение:

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

Если вы хотите проанализировать строку и вернуть правильный подкласс сообщения:

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

Наконец, если нет смысла создавать экземпляр Message, тогда он не должен быть классом. Это должна быть роль.

Конечно, вы можете справиться со строительством другого объекта. Просто убедитесь, что этот другой объект отвечает, например, только за понимание формата строки, а не за внутренности сообщения:

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 { ... }

Теперь вас больше не интересует наличие какого-то «суперкласса», отвечающего за создание «подклассов», что, я думаю, является лучшим дизайном. (Помните, что MessageString не имеет особой власти над классами, которые выполняют функцию «Сообщение». Это ключевой момент; он отвечает только за понимание сообщений со строкой.)

В любом случае, теперь вы просто:

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

(Вы знаете "MVC"? Это похоже.)

5 голосов
/ 07 августа 2009

Вы можете просто сделать:

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

Если это проще или лучше, решать вам.

4 голосов
/ 07 августа 2009

Ну, объект уже создан, когда вызывается BUILD, поэтому я бы сказал

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

Вы всегда можете переключиться с модели на основе наследования на модель на основе ролей, в которой вы создаете нужный объект (вместо передачи класса в класс фабрики), а затем применяете общую роль.

3 голосов
/ 07 августа 2009

Просто используйте другой объект фабрики для создания объектов этого класса.

Проще, гибче, надежнее и т. Д.

my $factory = Factory->new( ... factory parameters ... );</p> <p>my $object = $factory->new_object( ... various parameters ... );

где new_object может анализировать параметры и принимать решения как по данным внутри $factory, так и по данным этих параметров.

Когда вы поймете, что на следующем шаге вам понадобятся зависимые от кода объекты, обратите внимание на инверсию структуры управления.

...