我正在使用
Moose
roles在类中的某些存取方法中应用一些包装器行为.我想将这个角色应用于许多模块,每个模块都有一组不同的属性,我想要包装它们的访问器.有没有办法从角色中访问正在应用的模块的元类?即是这样的:
package My::Foo; use Moose; with 'My::Role::X'; has [ qw(attr1 attr2) ] => ( is => 'rw',# ... ); has 'fields' => ( is => 'bare',isa => 'ArrayRef[Str]',default => sub { [qw(attr1 attr2) ] },); 1; package My::Role::X; use Moose::Role; # this should be a Moose::Meta::Class object my $target_Meta = '????'; # get Class::MOP::Attribute object out of the Metaclass my $fields_attr = $target_Meta->find_attribute_by_name('fields'); # extract the value of this attribute - should be a coderef my $fields_to_modify = $fields_attr->default; # evaluate the coderef to get the arrayref $fields_to_modify = &$fields_to_modify if ref $fields_to_modify eq 'CODE'; around $_ => sub { # ... } for @$fields_to_modify; 1;
解决方法
看起来像
MooseX::Role::Parameterized会做到这一点:
Ordinary roles can require that its consumers have a particular list of method names. Since parameterized roles have direct access to its consumer,you can inspect it and throw errors if the consumer does not meet your needs. 07001
角色专业化的细节与增强的类保持一致;它甚至不需要传递任何参数,它需要知道的是传递给角色的参数(要包装的字段列表).唯一的关键是必须在类上定义相关属性后使用该角色.
因此,消费类和角色的定义如下:
package My::Foo; use Moose; my @fields = qw(attr1 attr2); has \@fields => ( is => 'rw',default => sub { \@fields },); with 'My::Role::X' => {}; 1; package My::Role::X; use MooseX::Role::Parameterized; role { my $p = shift; my %args = @_; # this should be a Moose::Meta::Class object my $target_Meta = $args{consumer}; # get Class::MOP::Attribute object out of the Metaclass my $fields_attr = $target_Meta->find_attribute_by_name('fields'); # extract the value of this attribute - should be a coderef my $fields_to_modify = $fields_attr->default; # evaluate the coderef to get the arrayref $fields_to_modify = &$fields_to_modify if ref $fields_to_modify eq 'CODE'; around $_ => sub { # ... } for @$fields_to_modify; }; 1;
附录:我发现如果参数化角色使用另一个参数化角色,那么嵌套角色中的$target_Meta实际上将是父角色的元类(isa MooseX :: Role :: Parameterized :: Meta :: Role ::参数化),而不是消费类的元类(isa Moose :: Meta :: Class).为了派生适当的元类,您需要将其显式传递为参数.我已将此作为“最佳实践”模板添加到我的所有参数化角色中:
package MyApp::Role::SomeRole; use MooseX::Role::Parameterized; # because we are used by an earlier role,Meta is not actually the Meta of the # consumer,but of the higher-level parameterized role. parameter Metaclass => ( is => 'ro',isa => 'Moose::Meta::Class',required => 1,); # ... other parameters here... role { my $params = shift; my %args = @_; # isa a Moose::Meta::Class my $Meta = $params->Metaclass; # class name of what is consuming us,om nom nom my $consumer = $Meta->name; # ... code here... }; # end role no Moose::Role; 1;
附录2:我进一步发现,如果将角色应用于对象实例而不是类,则角色中的$target_Meta实际上将是执行消耗的对象的类:
package main; use My::Foo; use Moose::Util; my $foo = My::Foo->new; Moose::Util::apply_all_roles($foo,MyApp::Role::SomeRole,{ parameter => 'value' }); package MyApp::Role::SomeRole; use MooseX::Role::Parameterized; # ... use same code as above (in addendum 1): role { my $Meta = $args{consumer}; my $consumer = $Meta->name; # fail! My::Foo does not implement the 'name' method
role { my $params = shift; my %args = @_; # could be a Moose::Meta::Class,or the object consuming us my $Meta = $args{consumer}; $Meta = $Meta->Meta if not $Meta->isa('Moose::Meta::Class'); # <-- important!