如何使用 Perl 和 Moose 编写工厂代码?

发布于 2024-07-30 17:20:37 字数 996 浏览 8 评论 0原文

是否有更简单或更好(=>更易于维护)的方法来使用 Perl 和 Moose< /a> 根据传入数据实例化类?

以下代码是我正在处理的项目中的精简示例。

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 时发生的情况。 根据您传递的参数,它将使用完全不同的代码,同时保持(大部分)一致的接口

Is there a simpler or better (=>easier to maintain) way to use Perl and Moose to instantiate classes based on incoming data?

The following code is a stripped down sample from a project I'm working on.

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

EDIT: It just struck me that this is pretty much what happens when you call the DBI. Depending on the parameters you pass, it will use entirely different code while maintaining a (mostly) consistent interface

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

榕城若虚 2024-08-06 17:20:37

恶心。 Stevan 有一个非常令人信服的论点,即 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;

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

Ick. Stevan has a very compelling argument that new should always only
return an instance of Class. Anything else is confusing to new people learning
the system.

You might wanna take a look at
MooseX::AbstractFactory.
If that won't work for you then:

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

Of course there are many other ways to implement this same concept in Moose. Without knowing the specifics of your domain problem it's hard to tell that something like MooseX::Traits wouldn't be better:

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;

This is roughly what the other poster meant about using a Role based solution.

墨小墨 2024-08-06 17:20:37

您可以简单地这样做:

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

是否更容易或更好由您决定。

You could simply do:

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

If that is easier or better is up to you to decide.

素衣风尘叹 2024-08-06 17:20:37

只是对一些答案的注释:

在 BUILD 中或 MOP 内部之外的任何地方调用 bless 总是不可接受的。 (如果你一定要反驳,有 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 对执行“Message”的类没有特殊的权力。这是这里的关键;它只负责理解字符串化消息。)

无论如何,现在您只需:(

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

Just a note on some of the answers:

Calling bless in BUILD, or anywhere outside of the MOP internals, is always unacceptable. (If you must rebless, there is Class::MOP::Class->rebless_instance!)

I second the advice on not allowing new to return anything other than an instance of __PACKAGE__. If you want a method that creates an instance of something, call it something else. Example:

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

Then, when you want to create a literal message:

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

When you want to parse a string and return the correct message subclass:

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

Finally, if it doesn't make sense to be able to create an instance of Message, then it should not be a class. It should be a role.

You can handle building with some other object, of course. Just make sure this other object is responsible only for understanding the string format, for example, and not message internals:

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

Now you are no longer concerned with having some "superclass" responsible for building "subclasses", which I think is better design. (Remember, MessageString has no special power over the classes that do "Message". That is the key here; it is only responsible for understanding stringified messages.)

Anyway, now you just:

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

(You know "MVC"? This is similar.)

青衫儰鉨ミ守葔 2024-08-06 17:20:37

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

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


my $factory = Factory->new( ... 工厂参数 ... );

my $object = $factory->new_object( ... 各种参数 ... );

其中 new_object 可以解析参数并根据 $factory 内部的数据和这些参数中的数据做出决策。

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

Just use another factory object to construct objects of that class.

Simpler, more flexible, more reliable, etc.


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

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

where new_object can parse the parameters and make decisions on both data inside $factory and data from those parameters.

When you figure out that you'll need codependent objects in the next step, look for an inversion of control framework.

折戟 2024-08-06 17:20:37

好吧,当调用 BUILD 时,该对象已经创建了,所以我想说,

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

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

Well, the object is already created when BUILD is called, so I would say

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

You may always wish to switch from an inheritance based model to a role based model where you create the object you want (rather than passing the class into the factory class), then apply the common role.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文