从方法修饰符在运行时应用角色

时间:2013-10-22 23:15:37

标签: perl moose

我有一个提供方法修饰符的角色:

package MyApp::Role::MenuExtensionRed;

use Moose::Role;
requires 'BuildMenu';

after 'BuildMenu' => sub {...};

由于其他地方的要求,我需要在运行时应用许多角色,如下所示:

package MyApp::MainMenu

before 'BuildMenu' => sub {
    my ( $self, $args ) = @_;

    my $roles  = $args->{menu_extensions};
    apply_all_roles($self,@$roles);
};

sub BuildMenu {...}

但是,永远不会调用'after'方法修饰符。显然我打破了一些规则,但我真的很想知道为什么这不起作用!

如果不是在'BuildMenu'之前应用角色,而是在BUILD方法中应用它们。但不幸的是,那时我的menu_extension角色列表不可用,所以我不得不等待。

任何替代解决方案都将受到赞赏!

编辑有趣的是,after 'BuildMenu'被调用,但仅限于后续调用BuildMenu。因此,方法的修饰符不能在其自己的修饰符中改变。这是预期的行为吗?有没有办法在运行时添加到修饰符的“列表”?

1 个答案:

答案 0 :(得分:2)

这是其实施方式的副作用。

使用方法修饰符时,它们基本上用新的方法替换现有方法,而新方法包含对旧方法的引用。

所以当你看到

before foo => sub {
}

角色组成时间会发生什么:

my $orig = *package::foo;
*package::foo = sub {
     $beforetrigger->( @args );
     return $orig->( @args );
}

或多或少。

所以想象一下,你有两个潜艇,“A”,在应用角色之前调用的BuildMenu版本,以及“B”,后面被调用的BuildMenu版本 应用角色。

所以会发生什么,你的通话顺序是这样的:

First Call ->
   A ->  
    before ->
         apply roles ->
           replace A with B
    <-- return from before
    A body   ->
    <-- return from A Body
Subsequent Call 
   B -> something

等等,我认为你需要做的是让你的包装代码确认这一点,并在申请后通过控制。

 around foo => sub { 
    my ( $orig, $self , @args ) = @_;
    # apply role to $self here
    # this gets the sub that will be resolved *after* the role is applied
    my $wrapped_sub = $self->can('foo');
    my $rval = $wrapped_sub->( $self, @args );      
    return $wrapped_sub->( $self, @args );
 }

请注意,该代码有可能有一个有趣的事情,其中​​$ wrapped_sub是我刚才写的应用角色的子,...我还没有确切知道会发生什么,但你可能需要在一些条件下,以防止发生自我呼唤 - 死亡循环。

但问题的要点基本上归结为这样一个事实:你不能用sub本身替换正在执行的子sub,并期望它自动菊花链,因为被替换的子是没有天生意识到它被替换,并且因为“方法修饰符”相当于用新链接替换subs而不是旧的=)