取势 明道 优术

作者为 扶 凯 发表

 


NAME

Moose::Manual::Roles – Roles, an alternative to deep hierarchies and base classes

 


VERSION

version 2.0401

 


什么是role

你不可以继承一个role,role也不可以被实例化。我们可以说,role是类和类之间或者role和其他role的消耗品.

换一种说话,role是一个类的内部组成.我们说一个类实现了一个role,那么这个role的所有属性,方法,方法修改器都已经包含到了这个类里面(我们有时候说这叫平滑过渡),即便这些属性和方法已经在类里面定义过了.子类也能继承到父类的role.

Moose Role和其他语言的interface比较相似.

除开可以在role定义属性和方法,role还可以要求消耗role的类实现指定的方法.你可以写一个role,这个role里面什么也没有, 只是定义了一些必须实现方法的列表.在这个方面,role和java的interface非常类似.

role在属性访问的时候使用非常常见.

 


一个简单的role

创建一个role很像创建一个class

  package Breakable;

  use Moose::Role;

  has 'is_broken' => (
      is  => 'rw',
      isa => 'Bool',
  );

  sub break {
      my $self = shift;

      print "I broke\n";

      $self->is_broken(1);
  }

除了在代码开头我们使用了the Moose::Role manpage,这看起来就像在定义moose的类.但是也就是仅此而已了,这个role不能实例化也不能被继承,只能被role和类来实现.

role的属性和方法都会包含到实现了这个role的class里面.

  package Car;

  use Moose;

  with 'Breakable';

  has 'engine' => (
      is  => 'ro',
      isa => 'Engine',
  );

with使得role嵌入了一个类,完成with操作之后,Car已经有一个is_broken的属性和一个break方法,Car也实现了这个role,我们叫做does<'Breakable');

  my $car = Car->new( engine => Engine->new );

  print $car->is_broken ? 'Busted' : 'Still working';
  $car->break;
  print $car->is_broken ? 'Busted' : 'Still working';

  $car->does('Breakable'); # true

这将会打印.

  Still working
  I broke
  Busted

同样我们可以在Bone里面dose这个role

  package Bone;

  use Moose;

  with 'Breakable';

  has 'marrow' => (
      is  => 'ro',
      isa => 'Marrow',
  );

更详细的例子我们可以参考the Moose::Cookbook::Roles::Recipe1 manpage

 


REQUIRED METHODS

之前已经提到过,role可以要求一个类必须实现某一个或者多个方法,继续我们Breakable的例子,我们要求消费类实现break方法.

  package Breakable;

  use Moose::Role;

  requires 'break';

  has 'is_broken' => (
      is  => 'rw',
      isa => 'Bool',
  );

  after 'break' => sub {
      my $self = shift;

      $self->is_broken(1);
  };

如果我们需要在class里面消费role但是确没有实现break方法,Moose会抛出一个异常给你.

好了,我们现在添加了一个break的方法,我们在break方法里面实现了自己break的逻辑,break方法被调用之后我们应该确认一下is_broken属性已经被设置了.

  package Car

  use Moose;

  with 'Breakable';

  has 'engine' => (
      is  => 'ro',
      isa => 'Engine',
  );

  sub break {
      my $self = shift;

      if ( $self->is_moving ) {
          $self->stop;
      }
  }

 

Roles 抽象类

如果你之前对c++之类的虚基类有所了解,那么你应该会猜到role的另外一些用途.

我们可以定义一个仅仅作为"interface"的role,role里面只是一些必须方法的列表.

但是,所有消耗这个role的类必须实现这些必须的方法,不管是直接实现还是通过父类来实现.

因为role定义了必须的一些公共的抽象的方法,但是确没有干任何事,可以说这事一个虚类,但是我们推荐你这样做,因为这样让接口变得统一,在外部调用的时候,我们看到的总会是同样的接口,很好的封装了代码.

 

Required Attributes

我们之前还提到role还可以定义必须的属性.但是这个属性的has操作是在runtime进行的,这意味着你必须在消耗这个role之前定义这个属性,否则这个role将看不到这个属性.

  package Breakable;

  use Moose::Role;

  requires 'stress';

  package Car;

  use Moose;

  has 'stress' => (
      is  => 'rw',
      isa => 'Int',
  );

  with 'Breakable';

 


使用 METHOD MODIFIERS

方法修改器和role组合在一起是非常强大的组合.

方法修改器会增加role的复杂性,但是确实非常有用.如果一个类实现了多个role,每一个role都修改了方法,但是通过role来管理不会混乱.我们看下面的代码.

  package MovieCar;

  use Moose;

  extends 'Car';

  with 'Breakable', 'ExplodesOnBreakage';

如果ExplodesOnBreakage方法also有一个在break上面的after修改,after修改将会一个接着一个进行.Breakable会最先运行,然后才是ExplodesOnBreakage.

 


方法冲突

如果一个class内部with了多个roles,而且这些role里面有很多名字相同的方法,情况会变得混乱起来.这时候我们就需要这个类实现自己的方法,并且name是一样的.

  package Breakdancer;

  use Moose::Role

  sub break {

  }

如果我们需要BreakableBreakdancer都过度到一个class里面,我们就必须提供我们自己的break方法.

  package FragileDancer;

  use Moose;

  with 'Breakable', 'Breakdancer';

  sub break { ... }

一个role还可以是其他role的集合.

  package Break::Bundle;

  use Moose::Role;

  with ('Breakable', 'Breakdancer');

 


方法别名

如果我们想要FragileDancer类可以正确调用每个role的相同名字的方法,我们可以为这些方法定义别名.

  package FragileDancer;

  use Moose;

  with 'Breakable'   => { -alias => { break => 'break_bone' } },
       'Breakdancer' => { -alias => { break => 'break_dance' } };

尽管这样干会有copy方法的消耗,而且我们还要干掉之前的break方法.

  with 'Breakable' => {
      -alias    => { break => 'break_bone' },
      -excludes => 'break',
      },
      'Breakdancer' => {
      -alias    => { break => 'break_dance' },
      -excludes => 'break',
      };

排除掉了break方法之后,我们就没有混淆的方法名了,这样FragileDancer也就没有必要去实现一个自己的break方法了.

这样做看起来是有用的,至少我们class里面不需要再实现break方法了,但是这么做是不值得的,因为这打破了代码的封装性,我们的class实现了BreakableBreakDancer的role,但是没有提供break的方法,如果一个其他API也实现了这2个role,它可能期望可以实现这个break的方法.

更详细的情况可以参考the Moose::Cookbook::Roles::Recipe2 manpage

 


ROLE 排除

一个role还可以声明这个role不能和某一个role一起使用,使用这个特性要非常小心,因为这会限制到role的可复用性.

  package Breakable;

  use Moose::Role;

  excludes 'BreakDancer';

 


添加一个role到对象

我们可以添加一个role到一个对象实例里面,而不是一个class.例如,你再debug的一个对象实例的时候,发现了一个特殊的bug需要这个role来帮助你追踪一些信息.

最好的方法就是通过调用Moose::Util里面的apply_all_roles方法来实现.

  use Moose::Util qw( apply_all_roles );

  my $car = Car->new;
  apply_all_roles( $car, 'Breakable' );

这个函数可以一次接受一个或者多role,调用之后和正常的role使用是一样的,因为这就是我们class里面使用with的内部实现.


AUTHOR

译者:斯文牛氓

 


NOTE

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

来了就留个评论吧! 没有评论