Perl Moose访问器即时生成

时间:2016-08-10 15:57:45

标签: perl attributes moose

请参阅以下基于Moose的Perl代码片段:

$BusinessClass->meta->add_attribute($Key => { is        => $rorw,
                                              isa       => $MooseType,
                                              lazy      => 0,
                                              required  => 0,
                                              reader    => sub { $_[0]->ORM->{$Key} },
                                              writer    => sub { $_[0]->ORM->newVal($Key, $_[1]) },
                                              predicate => "has_$Key",
                                            });

我收到错误:

bad accessor/reader/writer/predicate/clearer format, must be a HASH ref at /usr/local/lib/perl5/site_perl/mach/5.20/Class/MOP/Class.pm line 899

错误的原因很明显:读者和编写者必须是函数的字符串名称。

但在这个具体案例中该怎么做?我不想为每一百个ORM字段创建一个新函数(这里的ORM属性是一个绑定的哈希)。所以我不能在这里传递一个字符串,我需要一个闭包。

因此,我的编码需求导致了矛盾。我不知道该怎么做。

以上是真实代码的片段。现在我提出一个最小的例子:

#!/usr/bin/perl

my @Fields = qw( af sdaf gdsg ewwq fsf ); # pretend that we have 100 fields

# Imagine that this is a tied hash with 100 fields 
my %Data = map { $_ => rand } @Fields;

package Test;
use Moose;

foreach my $Key (@Fields) {
  __PACKAGE__->meta->add_attribute($Key => { is        => 'rw',
                                             isa       => 'Str',
                                             lazy      => 0,
                                             required  => 0,
                                             reader    => sub { $Data{$Key} },
                                             writer    => sub { $Data{$Key} = $_[1] },
                                           });
}

运行它会导致:

$ ./test.pl 
bad accessor/reader/writer/predicate/clearer format, must be a HASH ref at /usr/lib/i386-linux-gnu/perl5/5.22/Class/MOP/Class.pm line 899
    Class::MOP::Class::try {...}  at /usr/share/perl5/Try/Tiny.pm line 92
    eval {...} at /usr/share/perl5/Try/Tiny.pm line 83
    Try::Tiny::try('CODE(0x9dc6cec)', 'Try::Tiny::Catch=REF(0x9ea0c60)') called at /usr/lib/i386-linux-gnu/perl5/5.22/Class/MOP/Class.pm line 904
    Class::MOP::Class::_post_add_attribute('Moose::Meta::Class=HASH(0x9dc13f4)', 'Moose::Meta::Attribute=HASH(0x9dc6b5c)') called at /usr/lib/i386-linux-gnu/perl5/5.22/Class/MOP/Mixin/HasAttributes.pm line 39
    Class::MOP::Mixin::HasAttributes::add_attribute('Moose::Meta::Class=HASH(0x9dc13f4)', 'Moose::Meta::Attribute=HASH(0x9dc6b5c)') called at /usr/lib/i386-linux-gnu/perl5/5.22/Moose/Meta/Class.pm line 572
    Moose::Meta::Class::add_attribute('Moose::Meta::Class=HASH(0x9dc13f4)', 'af', 'HASH(0x9ea13a4)') called at test.pl line 18

我不知道该怎么做(如何创建"动态"(类似闭包)访问器,而不为每个100个字段编写单独的函数?)

1 个答案:

答案 0 :(得分:3)

我认为改变这样的读者和作者方法需要不健康的精神错乱。如果您愿意,请查看the source code of Class::MOP::Method::Accessor,它在引擎盖下用于创建访问者。

相反,我建议使用around方法修饰符覆盖(或附加)Moose生成的读者的功能。要使其与子类一起使用,您可以使用Class::Method::Modifiers代替Moose around

package Foo::Subclass;
use Moose;
extends 'Foo';

package Foo;
use Moose;

package main;
require Class::Method::Modifiers; # no import because it would overwrite Moose

my @Fields = qw( af sdaf gdsg ewwq fsf );    # pretend that we have 100 fields

# Imagine that this is a tied hash with 100 fields
my %Data = map { $_ => rand } @Fields;

my $class = 'Foo::Subclass';
foreach my $Key (@Fields) {
    $class->meta->add_attribute(
        $Key => {
            is       => 'rw',
            isa      => 'Str',
            lazy     => 0,
            required => 0,
        }
    );

    Class::Method::Modifiers::around( "${class}::$Key", sub {
        my $orig = shift;
        my $self = shift;

        $self->$orig(@_);    # just so Moose is up to speed

        # writer
        $Data{$Key} = $_[0] if @_;

        return $Data{$Key};
    });
}

然后进行测试。

package main;
use Data::Printer;
use v5.10;

my $foo = Test->new;
say $foo->sdaf;
$foo->sdaf('foobar');
say $foo->sdaf;

p %Data;
p $foo;

这是我机器上的STDOUT / STDERR。

{
    af     0.972962507120432,
    ewwq   0.959195914302605,
    fsf    0.719139421719849,
    gdsg   0.140205658312095,
    sdaf   "foobar"
}
Foo::Subclass  {
   Parents       Foo
    Linear @ISA   Foo::Subclass, Foo, Moose::Object
    public methods (6) : af, ewwq, fsf, gdsg, meta, sdaf
    private methods (0)
    internals: {
        sdaf   "foobar"
    }
}
0.885114977459551
foobar

正如您所看到的,Moose并不真正了解哈希中的值,但如果您使用访问器,它将读取和写入它们。使用编写器时,Moose对象将慢慢填满新值,否则Moose对象内部的值并不重要。