4

我有一个提供方法修饰符的角色,如下所示:

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 时调用。所以一个方法的修饰符不能从它自己的另一个修饰符中改变。这是预期的行为吗?有没有办法在运行时添加到修饰符的“列表”?

4

1 回答 1

2

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

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

所以当你看到

before foo => sub {
}

角色组合时发生的情况是:

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

或多或少。

所以想象一下,你有 2 个 subs,“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 是我刚刚编写的用于应用角色的子,......我还没有确切地知道那里会发生什么,但你可能需要放入一些防止发生自调用子死循环的条件。

但是问题的要点本质上归结为这样一个事实,即您不能用新的子程序从子程序本身替换正在执行的子程序,并期望它自动以菊花链形式连接,因为被替换的子程序本身并不知道它被替换了,并且因为“方法修饰符”相当于用链接到旧的新的替换子=)

于 2013-11-09T23:31:57.423 回答