取势 明道 优术

作者为 扶 凯 发表

 


名称

Moose::Manual::MethodModifiers – Moose's 的方法修饰符

 


版本

version 2.0401

 


什么是 METHOD MODIFIER?

Moose 提供了一个叫做 "method modifiers" 的特性,用来在你的程序执行前和执行后做一些操作.你也可以认为是 hook 或 advice .可以用在方法之前的可以用来增加和修改一些内容.之后的可以用来清理一些内容.

下面来看一个最简单的例子

  package Example;

  use Moose;

  sub foo {
      print "    foo\n";
  }

  before 'foo' => sub { print "about to call foo\n"; };
  after 'foo'  => sub { print "just called foo\n"; };

  around 'foo' => sub {
      my $orig = shift;
      my $self = shift;

      print "  I'm around foo\n";

      $self->$orig(@_);

      print "  I'm still around foo\n";
  };

现在我们来运行这段代码的输出 Example->new->foo();

  about to call foo
    I'm around foo
      foo
    I'm still around foo
  just called foo

你可能已经注意到了 before,after,around 这些语法糖的作用了.

正如你所知,它们的意义和字面上的意义是一样的.用在方法之前和方法之后做一些相关的操作. around 是包装方法做一些事情.

当有多个 before after aroud 的时候,它们的执行顺序又是什么样的呢,下面是我的一段测试代码:

    package Method;
    use Moose;

    has 'method'=>( is => 'rw' );

    before 'call'=> sub { print "   1 before call method modify\n" };
    before 'call'=> sub { print "2 before call method modify\n" };

    around 'call'=> sub {
        my $orig = shift;
        my $self = shift;

        print "         this in call method\n";
        $self->$orig();
        print "         call run finish \n";
    };

    after 'call'=> sub{
        print "1 after run method modifier\n";
    };
    after 'call'=> sub{
        print "     2 after run method modifier\n";
    };
    sub call{
        print "this a primary call method\n";
    }

    1;

    my $o = Method->new( method => 'call' );
    $o->call;

值得注意的是 before 的 method 修改不是顺序运行的 而是逆序运行,其实这也符合我们的思维习惯,在前面的总是在最前面,在后面的总是在最后面.至于 Moose 内部如何实现的,这里就不再啰嗦了.

 


WHY USE THEM?

方法修改器有很多用途.结合 roles 可以对 class 里面的方法进行包装.

 


BEFORE, AFTER, AND AROUND

Method modifiers can be used to add behavior to methods without modifying the definition of those methods.

 

BEFORE 和 AFTER modifiers

方法修改器可以改变方法的行为,比如属性访问

  has 'size' => ( is => 'rw' );

  before 'size' => sub {
      my $self = shift;

      if (@_) {
          Carp::cluck('Someone is setting size');
      }
  };

before modifier 还可以用来在方法调用之前,来检测方法或者属性的状态,看下面的例子:

  before 'size' => sub {
      my $self = shift;

      die 'Cannot set size while the person is growing'
          if @_ && $self->is_growing;
  };

这里我们实现了一个检测的逻辑,当 size 还是在增长的时候,我们是不能设置 size 的,这里我们通过 before 实现该逻辑.

同样的,我们在输入用户名密码之后我们可以利用 after 的特性来验证登陆.

 

AROUND modifiers

around 修饰比 before 和 after 更为强大,因为 around 可以修改方法内部的行为,当你想在方法内部添加一些代码块的时候,around 就可以派上用场了.

around 的第一个参数为原始方法的方法名,第二个参数为对象实例,随后的是传入的参数.

  around 'size' => sub {
      my $orig = shift;
      my $self = shift;

      return $self->$orig()
          unless @_;

      my $size = shift;
      $size = $size / 2
          if $self->likes_small_things();

      return $self->$orig($size);
  };

 

一次封装多个方法

before, after, around 也可以批量定义,只需要简单的象下面的例子一样,使用一个列表就行了:

  before [qw(foo bar baz)] => sub {
      warn "something is being called!";
  };

在这,会给 before 增加到当前类的 foo, barbaz 方法上.

我们还可以通过for loop来逐个指定

  for my $func (qw(foo bar baz)) {
      before $func => sub {
          warn "$func was called!";
      };
  }

 

通过正则来选择要封装的方法

甚至我们还可以通过正则来添加 after 方法.

  after qr/^command_/ => sub {
      warn "got a command";
  };

上面的代码将会在所有的 command_ 开头的方法 get_method_list in the Class::MOP::Class manpage 后面添加 after 代码里面的 warn.

表面上看起来配合正则匹配方法在这里非常方便,但是也非常危险的,因为 Moose 内部的 meta, new, BUILD, DESTROY, AUTOLOAD 方法是不容许这么操作的,所以我们在匹配方法的时候要尽量避开匹配到这些方法.

 


INNER 和 AUGMENT

Augment 和 inner 是相同的功能一半.augment 修饰符提供了一个子类的调用的例的排序,会翻转内部调用顺序,这会颠覆我们传统的类方法调用顺序. 这个需要超类执行工作的一部分,所以当调用超类时,超类会调用通过 inner 调用子类的这个方法,多级子类时,并通过 inner 的返回一层层向下传.

当在子类中调用 augment modifier 时, Document 会调用 inner() 取得子类的内容,并插入:

  package Document;

  use Moose;

  sub as_xml {
      my $self = shift;

      my $xml = "<document>\n";
      $xml .= inner();
      $xml .= "</document>\n";

      return $xml;
  }

通过 inner() 的方法,然后在我们的多个子类中使用 augment 的方法来增强的方法的实现:

  package Report;

  use Moose;

  extends 'Document';

  augment 'as_xml' => sub {
      my $self = shift;

      my $xml = "  <report>\n";
      $xml .= inner();
      $xml .= "  </report>\n";

      return $xml;
  };

当我们在 Report 的对象中调用 as_xml 的时候, 我们会通过 inner() 返回到父类的 as_xml 方法,因此会有下面的输出:

  <document>
    <report>
    </report>
  </document>

因为我们在 Report 中也调用了 inner() ,所以我们可以包含更多的子类来增加更多的内容到这个 document 中:

  package Report::IncomeAndExpenses;

  use Moose;

  extends 'Report';

  augment 'as_xml' => sub {
      my $self = shift;

      my $xml = '    <income>' . $self->income . '</income>';
      $xml .= "\n";
      $xml .= '    <expenses>' . $self->expenses . '</expenses>';
      $xml .= "\n";

      $xml .= inner() || q{};

      return $xml;
  };

以此类推,我们会有以下的输出:

  <document>
    <report>
      <income>$10</income>
      <expenses>$8</expenses>
    </report>
  </document>

为什么使用 augmentinner() 这种组合比较特殊,它使我们能够从父(比较具体)到子(最具体的)的实现类.这颠倒了正常的层次关系.

请注意,Report::IncomeAndExpenses 中会再次调用 inner().如果对象是一个实例对象 Report::IncomeAndExpenses,那么这个调用是 no-op,并返回 false.这总是调用inner() 是一个好主意,以便为将来扩一个子类.

 


OVERRIDE 和 SUPER

此外,Moose 还提供了很多简单的语法糖来覆写 Perl 的内置方法.如果你想覆写一个父类的方法,你只需简单的声明 override

  package Employee;

  use Moose;

  extends 'Person';

  has 'job_title' => ( is => 'rw' );

  override 'display_name' => sub {
      my $self = shift;

      return super() . q{, } . $self->title();
  };

这里的 super() 几乎等同于 <$self-SUPER::display_name>>,唯一不同的地方就是传入超类方法的参数总是和传入 overridden 部分的参数一样,并且不能更改. 一个很简单的例子就是:

  override 'display_name' => sub {
        my $self  = shift;

        return super('ooxx').q{,}.$self->title();
  };

上面的代码和之前的输出是一模一样的,super 并不会处理你所传入的参数,因为在你传递参数之前,super 已经处理了@_

 


分号

因为所有的方法修改器都是通过 perl 实现的,所以必须显示声明方法修改的结束

  after 'foo' => sub { };

 


警告

这些 method modification 功能如果在多重继承时不能工作,这要怎么解决,先确保你的类层次结构是和你预期一样运作的,或者最好不要使用多重继承( role 可以帮助你)!

 


AUTHOR

Moose is maintained by the Moose Cabal, along with the help of many contributors. See Moose/CABAL and Moose/CONTRIBUTORS for details.

译者:斯文牛氓 审查:扶凯

 


NOTE

本文由个人根据 cpan 翻译,可能由于水平有限,译文质量望读者见谅,不屑一顾者请绕道远行,英文好的请直接看原著(最好也帮助翻译). — PerlChina 重建计划作品

来了就留个评论吧! 1个评论



    kingwmj 2011年12月21日 的 09:40

    太好了,又见Moose