【问题标题】:Moose construct objects from single argumentsMoose 从单个参数构造对象
【发布时间】:2015-06-26 12:19:11
【问题描述】:

我已经涉足 Moose 大约 7 个月了,而 Perl 只是稍长一些,但无法弄清楚如何通过为每个属性提供一个参数而不是它们的整个 hashref 来构造一个类中的多个属性.我已经广泛搜索了文档和网络,但我要么在寻找错误的单词,要么遗漏了一些东西。

我已将设计调整为更通用。使用以下基本设置:

package First;
use Moose;
use Second::Type1;
use Second::Type2;
has 'type1' => (
  is => 'rw',
  isa => 'Second::Type1',
  default => sub {Second::Type1->new(name => 'random')}
);

has 'type2' => (
  is => 'rw',
  isa => 'Second::Type2',
  default => sub {Second::Type2->new(name => 'random')}
);

package Second::Type1;
use Moose;
use This;
has 'name' => (
  is => 'rw',
  isa => 'Str',
  required => 1,
);
has 'this' => (
  is => 'rw',
  isa => 'This',
  default => sub {This->new()}
);
# package has more attributes, but you get the idea
__PACKAGE__->meta->make_immutable();
no Moose;
1;

package Second::Type2;
use Moose;
use That;
has 'name' => (
  is => 'rw',
  isa => 'Str',
  required => 1,
);
has 'that' => (
  is => 'rw',
  isa => 'That',
  default => sub {That->new()}
);
# package has more attributes, but you get the idea
__PACKAGE__->meta->make_immutable();
no Moose;
1;

我希望能够通过以下方式构造一个 First:

use First;
my $first = First->new(type1 => 'foo', type2 => 'bar');

其中 'foo' 等于 Second::Type1 的 'name' 属性的值,而 'bar' 等于 Second::Type2 的 'name' 属性的值。

现在,至于我自己的解决方案,我(成功地)制作了一个 Moose::Role,它只包含一个“around BUILDARGS”子,然后使用一个工厂类(其内容与 IMO 无关):

package Role::SingleBuildargs;

use Moose::Role;
use Factory::Second;
requires 'get_supported_args';

around BUILDARGS => sub {
my ($class, $self, %args) = @_;
my @supported_args = $self->get_supported_args;
my $factory = Factory::Second->new();
    my @errors = ();
    foreach my $arg (sort {$a cmp $b} keys %args) {
        if (grep {$_ eq $arg} @supported_args) {
            my $val = $args{$arg};
            if (!ref $val) {    # passed scalar init_arg
                print "$self (BUILDARGS): passed scalar\n";
                print "Building a Second with type '$arg' and name '$val'\n";
                $args{$arg} = $factory->create(type => $arg, name => $val)
            } elsif (ref $val eq 'HASH') {  # passed hashref init_arg
                print "$self (BUILDARGS): passed hashref:\n";
                my %init_args = %$val;
                delete $init_args{name} unless $init_args{name};
                $init_args{type} = $arg;
                $args{$arg} = $factory->create(%init_args);
            } else {    # passed another ref entirely
                print "$self (BUILDARGS): cannot handle reference of type: ", ref $val, "\n";
                die;
            }
        } else {
            push @errors, "$self - Unsupported attribute: '$arg'";
        }
    }
    if (@errors) {
        print join("\n", @errors), "\n";
        die;
    }
    return $self->$class(%args);
    };

no Moose;
1;

然后我在 First 类和其他类(如 First)中使用该角色。

我也试过通过以下方式强制:

package Role::Second::TypeConstraints;
use Moose::Util::TypeConstraints

subtype 'SecondType1', as 'Second::Type1';
subtype 'SecondType2', as 'Second::Type2';
coerce 'SecondType1', from 'Str', via {Second::Type1->new(name => $_};
coerce 'SecondType2', from 'Str', via {Second::Type2->new(name => $_};

no Moose::Util::TypeConstraints;
1;

并修改了第一个包(仅列出更改):

use Role::Second::TypeConstraints;
has 'type1' => (
   isa => 'SecondType1',
   coerce => 1,
);
has 'type2' => (
   isa => 'SecondType2',
   coerce => 1,
);    

但是没有用。如果有人能解释原因,那就太好了。

至于实际问题:在您的课程中获得这种行为的最佳方式是什么?真的没有比修改 BUILDARGS 更好的方法,还是我错过了一些东西(也许是关于 Moose::Util::TypeConstraints)? TMTOWTDI 等等,但我的似乎根本没有效率。

编辑:为保持一致性而编辑(混淆了通用类名)

【问题讨论】:

  • 我过去曾使用强制执行过此类操作。无法直接看出为什么您现有的强制设置不这样做。您已经记住了将强制元属性添加到您希望强制发生的属性中通常是主要的绊脚石。我建议坚持使用强制手段。
  • 这正是BUILDARGS 的用途。

标签: perl moose type-constraints


【解决方案1】:

您可以完全按照您的描述使用强制转换

  • 添加

    use Moose::Util::TypeConstraints;
    

    FirstSecond::Type1Second::Type2

  • Second::Type1添加强制操作

    coerce 'Second::Type1'
        => from 'Str'
            => via { Second::Type1->new( name => $_ ) };
    

    Second::Type2

    coerce 'Second::Type2'
        => from 'Str'
            => via { Second::Type2->new( name => $_ ) };
    
  • Firsttype1type2 属性启用强制转换

    has 'type1' => (
      is      => 'rw',
      isa     => 'Second::Type1',
      default => sub { Second::Type1->new },
      coerce  => 1,
    );
    
    has 'type2' => (
      is      => 'rw',
      isa     => 'Second::Type2',
      default => sub { Second::Type2->new },
      coerce  => 1,
    );
    

然后你可以完全按照你说的创建一个First对象,用

my $first = First->new(type1 => 'foo', type2 => 'bar');

【讨论】:

  • 您的解决方案似乎比仅仅为了一个类而定义自定义类型更优雅,就像我所做的那样。我试试看。
  • 现在试过了。像你说的那样工作。为清楚起见:“使用 Moose::Util::TypeConstraints;”还需要将任何强制添加到“First”包中才能对其属性起作用,但我确信这在你的帖子中是隐含的,Borodin。
  • @etripe:很高兴你有一个解决方案。我忽略了那个修改,谢谢。我已将其添加到我的帖子中。顺便说一句,我认为 default 项不正确,因为构造函数需要 name 属性才能工作
  • @etripe:我不确定设置默认值是否有意义,尤其是在没有任何唯一性检查的情况下。但这在很大程度上取决于您的实际应用程序的设计,因此完全取决于您。我想,有了强制,你可以写 default = 'Default Type2' 而不是调用构造函数
【解决方案2】:

你绝对可以通过类型和强制来做到这一点。尝试使用MooseX::Types 而不是内置的。

这是一个小例子,部分摘自 MooseX::Types 文档。

package Foo;
use Moose;
use MooseX::Types -declare => [qw(MyDateTime)];
use MooseX::Types::Moose 'Int';
use DateTime;

class_type MyDateTime, { class => 'DateTime' };
coerce MyDateTime, from Int, via { DateTime->from_epoch( epoch => $_ ) };

has date => (
    is      => 'ro',
    isa     => MyDateTime,
    default => sub {time},
    coerce  => 1,
);

package main;

my $foo = Foo->new;
say 'without args: ', $foo->date;

my $bar = Foo->new( date => time - 24 * 3600 );
say 'with args:    ', $bar->date;

这将打印如下内容:

without args: 2015-06-26T13:35:38
with args:    2015-06-25T13:35:38

它看起来很像你所拥有的,只是使用了更新类型的东西。试一试。

【讨论】:

  • 感谢您的建议。我会调查你说的。我还将查看文档以了解通过使用 MooseX::Types 而不是通常的 Moose::Util::TypeConstraints 获得了什么。
猜你喜欢
  • 1970-01-01
  • 2023-03-31
  • 2018-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多