【问题标题】:Moose applies method modifiers twiceMoose 两次应用方法修饰符
【发布时间】:2018-02-10 11:17:09
【问题描述】:

基本架构

我在perl中构建了一个信息检索工具,使用Moose作为框架。

我有一个插件类层次结构,其中Base 作为插件的公共基类,特定于访问方法的插件从该类继承(方法是 HTTP、FTP、IMAP...)。

从这些子类中,实际的工作类继承(每个数据源一个插件)。

我使用 Moose 角色将特定于源的行为组合到实际的工作类中(例如在 HTTP 源中启用对 SSL 客户端证书的支持)。

问题

其中一个方法特定类 (Base::A) 需要角色 R。相同的角色R 也被角色S 使用,然后被工作类X 使用,继承自Base::A

我的问题是R 中的方法修饰符被两次应用于X。有没有办法阻止 Moose 将方法修饰符应用于已应用于某个父类的类?

示例

#!/usr/bin/env perl

use strict;
use warnings;
use utf8;

use v5.14;

{
    package R;

    use Moose::Role;

    before 'bar' => sub { say "R::before'bar'()" }
}

{
    package S;

    use Moose::Role;
    with 'R';

    before 'bar' => sub { say "S::before'bar'()" }
}

{
    package Base;

    use Moose;

    sub foo { say "Hello foo()"; }
}

{
    package Base::A;

    use Moose;
    extends 'Base';
    with 'R';

    sub bar { $_[0]->foo(); say "Hello bar()"; }
}

{
    package X;

    use Moose;
    extends 'Base::A';
    with 'S';
}


package main;

my $a = X->new();

$a->bar();

实际输出

S::before'bar'()
R::before'bar'()
R::before'bar'()
Hello bar()

预期输出

R::before'bar'() 行应该只出现一次。

【问题讨论】:

    标签: perl traits moose


    【解决方案1】:

    您可以使用参数化角色来防止包装子:

    #! /usr/bin/perl
    use warnings;
    use strict;
    use feature qw{ say };
    
    {   package R;
        use MooseX::Role::Parameterized;
    
        parameter should_wrap_bar => (
            isa => 'Bool',
            default => 1,
        );
    
        role {
            my ($param) = @_;
            before 'bar' => sub { say "R::before'bar'()" }
                if $param->{should_wrap_bar};
        };
    }
    
    {   package Base;
        use Moose;
        with 'R';
    
        sub foo { say "Hello foo()"; }
        sub bar { $_[0]->foo(); say "Hello bar()"; }
    }
    
    {   package X;
        use Moose;
        extends 'Base';
        with R => { should_wrap_bar => 0 };
    }
    
    package main;
    X->new->bar;
    

    【讨论】:

      【解决方案2】:

      首先,您的示例可以简单得多:

      {
          package R;
          use Moose::Role;
          before 'bar' => sub { say "R::before'bar'()" }
      }
      
      {
          package Base;
          use Moose;
          with 'R';
      
          sub foo { say "Hello foo()"; }
          sub bar { $_[0]->foo(); say "Hello bar()"; }
      }
      
      {
          package X;
          use Moose;
          extends 'Base';
          with 'R';
      }
      
      
      package main;
      
      X->new()->bar();
      

      输出是:

      R::before'bar'()
      R::before'bar'()
      Hello foo()
      Hello bar()
      

      为什么

      我同意这有点出乎意料,但如果你仔细想想,这一切都是有道理的。角色不是基类,角色不是实现的接口(参见 Java),角色甚至不是 Python 意义上的“mixins”(在 Python 中,我们实际上确实从 mixins 继承,但这只是语言限制)。角色只是你应用到你的类的一堆特性(属性、方法、修饰符等)。这是一次性行动。具有角色的类不会“记住”它,它只是在创建类时应用。你不是从角色继承的,所以你不应该期望 Moose 实现一些 diamond 来合并同一角色的多个应用程序。

      另一方面,如果您尝试执行with qw(R S);,那么R 令人惊讶的是(或者可能不是真的)只应用一次。

      做什么

      现在回到实际问题。由于您希望您的“之前”相互覆盖,您可以完全放弃使用 before 并将其重构为一个简单的方法(就像您在任何其他不支持此类修饰符的语言中所做的那样):

      sub bar {
          my ($self) = @_;
      
          $self->_before_bar_hook();
          # ...
      }
      
      sub _before_bar_hook {}
      

      结论

      前后修饰符和角色都是相当高级的 Moose 功能,我对一些奇怪的副作用(如您发现的那样)并不感到惊讶。虽然我相信我的解释大部分是正确的,但我不建议使用需要这种解释的东西。

      我个人完全避免使用 before/after 修饰符,因为我更喜欢显式调用挂钩(如上所示)。

      【讨论】:

      • 我不希望我的“之前”相互覆盖,如果之前已应用于某个父类,我希望通过角色组合应用于类的之前不被应用。
      • 这不会改变任何事情。类不记得应用了哪些“之前”。
      猜你喜欢
      • 2013-02-02
      • 1970-01-01
      • 2013-09-02
      • 2012-10-30
      • 2010-12-14
      • 2011-06-25
      • 2012-09-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多