12

是否有更简单或更好(=> 更易于维护)的方式来使用 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 时,这让我很震惊。根据您传递的参数,它将使用完全不同的代码,同时保持(大部分)一致的界面

4

5 回答 5

11

伊克。Stevan 有一个非常有说服力的论点,即new应该始终只返回 Class 的一个实例。其他任何事情都会让学习该系统的新人感到困惑。

您可能想看看 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;

这大致就是另一张海报关于使用基于角色的解决方案的意思。

于 2009-08-07T14:32:32.843 回答
6

你可以简单地做:

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

如果这更容易或更好由您决定。

于 2009-08-07T12:12:49.360 回答
5

只是对一些答案的注释:

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”吗?这很相似。)

于 2009-08-07T18:02:22.067 回答
4

只需使用另一个工厂对象来构造该类的对象。

更简单、更灵活、更可靠等。

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

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

wherenew_object可以解析参数并对内部$factory数据和来自这些参数的数据做出决策。

当您发现下一步需要相互依赖的对象时,请寻找控制反转框架。

于 2009-08-07T18:19:06.573 回答
3

好吧,对象在被调用时已经创建BUILD,所以我会说

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

您可能总是希望从基于继承的模型切换到创建所需对象的基于角色的模型(而不是将类传递给工厂类),然后应用公共角色。

于 2009-08-07T13:08:12.607 回答