Perl Moose方法修饰符:在“before”和“after”之前调用“around”。

5

我正在使用Moose,需要对项目中的方法调用进行包装。对于我的包装代码来说,最外层的修饰符非常重要。到目前为止我所做的是将我的方法修饰符放在Moose Role中,然后像这样在类的结尾应用该Role:

use Moose::Util;
Moose::Util::apply_all_roles(__PACKAGE__->meta, ('App:Roles::CustomRole'));
__PACKAGE__->meta->make_immutable;

这使我相当确信我的角色修饰符是最后定义的,因此可以正确地使用“before”和“after”的行为。(角色中的“before”和“after”分别被称为“very first”和“very last”)。
我最初认为这已经足够了,但现在我真的需要用“around”以类似的方式包装方法。Moose所依赖的Class::MOP会先应用“around”修饰符,因此它们在“before”之后和“after”之前被调用。
更详细地说,这是我的修饰符当前的调用顺序:
CUSTOM ROLE before
    before 2
       before 1
           CUSTOM ROLE around
               around
                   method
               around
           CUSTOM ROLE around
       after 1
    after 2
 CUSTOM ROLE AFTER

我真的需要像这样的东西:

CUSTOM ROLE before
    CUSTOM ROLE around
        before 2
           before 1
               around
                   method
               around
           after 1
        after 2
    CUSTOM ROLE around
 CUSTOM ROLE AFTER

有没有什么办法可以让我的“around”修饰符应用/调用在我想要的地方?我知道我可以做一些符号表操作(就像Class :: MOP已经在做的那样),但我真的不想这样做。

1
我同意下面Ether的问题,为什么你要使用Moose::Util::apply_all_roles而不是with - perigrin
我希望在我的角色中,'before'和'after'修饰符能够比已经存在于类中的其他修饰符更早或更晚地运行。手动应用角色会在后面定义修饰符,然后修饰符会首先运行(对于'before')和最后运行(对于'after')。 - Matt Wood
@perigrin,我终于理解了你和Ether的问题。我不需要像我想的那样使用apply_all_roles来应用角色。我还很新手Moose,在需要手动应用角色的想法上卡住了。我真正需要做的就是在文件末尾(在其他修饰符之后)使用“with”来应用它,而不是在开头。 - Matt Wood
2个回答

5

最简单的解决方案是让自定义角色定义一个调用主方法的方法,然后进行包装。

role MyRole { 
    required 'wrapped_method';
    method custom_role_base_wrapper { $self->wrapped_method(@_) }

    around custom_role_base_wrapper { ... }
    before custom_role_base_wrapper { ... }
}

你遇到的问题是,你试图让CUSTOM ROLE包装除了方法以外的其他内容。这不是它设计的目的。除了编写类似于你建议的符号表黑客技术(也许你可以说服Moose的某个人在Class::MOP中公开一个API来帮助解决这个问题),我能想到的唯一解决方案就是上面提到的那个。
如果你不想要custom_role_base_wrapper添加的额外调用堆栈帧,你应该看一下Yuval的Sub::Call::Tail或使用goto来操作调用堆栈。

我最终采用了自定义包装器的想法。这给了我所需的灵活性,谢谢。 - Matt Wood

3

我对Moose还比较新,但你为什么要这样做:

use Moose::Util;
Moose::Util::apply_all_roles(__PACKAGE__->meta, ('App:Roles::CustomRole'));

而不仅仅是这个吗?
with 'App:Roles::CustomRole';

关于你的问题,这可能有点取巧,但是你能否将你的around方法拆分成beforeafter方法,并在类定义的末尾应用该角色(以便按照你所需的顺序应用)?如果绝对必要,你可以使用私有属性来保存两个方法之间的状态。


1
问题在于,无论是before还是after都不能像around一样干净地改变返回语义。如果这些语义很重要,那么你就完了。如果它们不重要,那么你为什么要使用around呢? - perigrin
我手动应用的原因是修饰符按照定义顺序添加(然后运行)。通常你不关心修饰符的运行顺序。但是,在我的情况下,我希望我的角色修饰符在最前面和最后面运行。如果您使用“with”语法来应用角色,则其修饰符将首先被定义,因此它们将作为最内部的“before”和“after”运行。通过在最后手动应用角色,它们最后被定义并且在我想要它们运行时运行。 - Matt Wood
@Matt:这似乎确实是Moose的一个缺点。也许after语义应该允许像around一样改变返回值,或者beforearoundafter方法的顺序应该被修改,使它们相互之间都是LIFO,而不仅仅是对自身(如果这有意义的话)。 - Ether
“before”和“after”被有意设计为不包含此类副作用。具体来说,“before”和“after”应该用于附加到方法的带外行为,而“around”则用于这种方法的“覆盖”。改变三年前的语义并不是Moose团队愿意接受的,我也认为这并不是答案。 - perigrin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接