Как я могу кодировать factory в Perl и Moose?

Есть ли более простой или лучший (= > проще поддерживать) способ использования 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)[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

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

Ответ 1

Ик. У Стевана есть очень веский аргумент, что 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

Конечно, есть много других способов реализации этой же концепции в Музе. Не зная специфики проблемы вашего домена, трудно сказать, что что-то вроде 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;

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

Ответ 2

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

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

Если вам будет легче или лучше решить.

Ответ 3

Просто обратите внимание на некоторые ответы:

Вызов bless в BUILD или где-либо вне внутренних функций MOP всегда неприемлем. (Если вы должны безвозвратно, есть 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!' );

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

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

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"? Это похоже.)

Ответ 4

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

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

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

Ответ 5

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

Более простой, более гибкий, надежный и т.д.

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

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

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

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