取势 明道 优术

作者为 扶 凯 发表

本文由 斯文牛氓@DJBing 所译.经他的同意放到网上..

 


Moose手册-属性

Moose::Manual::Attributes – Object attributes with Moose

 


版本

version 2.0205

 


介绍

Moose attribute 内置了很多语法糖来让perl oop处理属性更加方便,所谓的属性 也就是对象的属性,例如Person的属性有名字,First Name和Last Name,而且有些对象的属性是可选的,有些对象 的属性是必须的,有些属性是有默认值的,以及属性的类型,所有的这些,Moose都可以帮助你来实现,我们可以调用 perl内置的has,delegation,predicate来实现.

 


属性设置

首先看一个最简单的例子

  package Person;

  use Moose;

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

上面的代码声明了一个Person类,每一个Person对象都有一个first_name的属性,is => 'rw',说明该对象可以 被read-write.

 

可读可写 vs 只读

所谓的Read-write => rw ,Read-only => ro,分别表示可读写和可读,对应的要改写一个对象属性,需要提供对应的 读写method,默认的Moose需要你提供属性的权限,如果你忘记写了,Moose会抛出一个警告.

 

访问器

每一个attribute都有一个或者多个访问器(accessor methods).我们通过访问器来读取或者改变对象的属性

  has 'weight' => (
      is     => 'ro',
      writer => '_set_weight',
  );

有的时候,这段代码是非常有用的,如果weight是基于其他method来计算出来的,我们设置了一个'_set_weight'的method, 当每次被调用的时候我们通过这个method来改变weight的值,_set_weight是对应的方法名.这里的Writer也就是access method.

甚至,你可以分别为read和write定义不同的method

  has 'weight' => (
      is     => 'rw',
      reader => 'get_weight',
      writer => 'set_weight',
  );

但是这么做的后果,一来你会觉得代码冗余,二来这样定义是不稳定的,如果你想了解更多的话, 请参考the Moose::Manual::MooseX manpage

 

断言和清理 方法

Moose允许你查看一个属性是不是已经定义,如果已经定义返回true,否则返回false. Moose实现这样的机制是通过predicate来实现的,姑且称之为断言吧,值得注意的是, 即便你指定一个属性的值为undef,在执行predicate的时候,返回是为true的,因为你已经 定义了这个属性为undef. 对应我们要清理设置的属性值,使用clearer来实现. 这2个方法我们都只用定义对应的方法名,我们并不需要自己来实现它的代码,因为Moose已经 为我们生成了这些方法,你所要做的只是明确的指定一个method的name.

  package Person;

  use Moose;

  has 'ssn' => (
      is        => 'rw',
      clearer   => 'clear_ssn',
      predicate => 'has_ssn',
  );

  ...

  my $person = Person->new();
  $person->has_ssn; # false

  $person->ssn(undef);
  $person->ssn; # returns undef
  $person->has_ssn; # true

  $person->clear_ssn;
  $person->ssn; # returns undef
  $person->has_ssn; # false

  $person->ssn('123-45-6789');
  $person->ssn; # returns '123-45-6789'
  $person->has_ssn; # true

  my $person2 = Person->new( ssn => '111-22-3333');
  $person2->has_ssn; # true

 

必需

Moose默认所有attribute是可选的,如果某些属性是你所必须的,你需要在has后面的设置里设置 require => 1,这样表明这个属性是必须的,如果你使用$Person->new()创建对象的时候你没有提供name的值 ,moose会抛出一个异常.

  has 'name' => (
      is       => 'ro',
      required => 1,
  );

如果接着你定义了一个clearer,这也是有效的,所以即便你指定了一个属性是必须的,但是由于clearer的存在, 该属性还是有可能是未定义的,但是我们并不推荐这样的做法.

 

默认值和构建方法

Attributes 是可以有默认值的,Moose提供两种方法来指定默认值.

最简单的形式,你可以给它指定一个scalar

  has 'size' => (
      is        => 'ro',
      default   => 'medium',
      predicate => 'has_size',
  );

如果该属性在对象构造的时候没有指定值,那么size就被指定为default里面设置的'medium'

  my $person = Person->new();
  $person->size; # medium
  $person->has_size; # true

同样你还可以指定一个闭包作为默认值,如下面的生成一个随机的size,moose会为你调用这个闭包.

  has 'size' => (
      is => 'ro',
      default =>
          sub { ( 'small', 'medium', 'large' )[ int( rand 3 ) ] },
      predicate => 'has_size',
  );

这是一个典型的例子,但是它的问题在于每次你创建一个对象并且没有初始化size,这个闭包都要被调用一次.

当你提供了一个闭包,Moose对象会像调用方法一样来调用他,如果熟悉perl的童鞋应该不会对此感到陌生吧.

  has 'size' => (
      is      => 'ro',
      default => sub {
          my $self = shift;

          return $self->height > 200 ? 'large' : 'average';
      },
  );

但是问题出现了,上面的size是决定于height的,而且default的优先级是优先于其他属性的设置的,Moose当然想到了 这一点,所以在moose内部是可以设置一个attribute为lazy的,lazy,顾名思义,就是把它放到最后再执行.

如果你想定义一个reference,必须设定一个闭包来返回这个reference,

  has 'mapping' => (
      is      => 'ro',
      default => sub { {} },
  );

而像下面这样是错误的.

  has 'mapping' => (
      is      => 'ro',
      default => {}, # wrong!
  );

你可能觉得这有点太古怪,但是请原谅,perl有时候就是这样干活的.

另外一个设置初始化值的方法就是设定一个builder,

  has 'size' => (
      is        => 'ro',
      builder   => '_build_size',
      predicate => 'has_size',
  );

  sub _build_size {
      return ( 'small', 'medium', 'large' )[ int( rand 3 ) ];
  }

这样做是有很多有点的,他可以明确指定一个命名方法,而不是一个闭包,然后因为这个方法是有名字的而不是匿名的 函数,这样你可以在子类重载它或者由一个role来实现.

我们强烈推荐你用bulider来初始化一个attribute,他就是default的加强版.

 

Builders覆写

由于builder是可以被overridable,例如下面我们继承一个父类,并且override他的_build_size method.

  package Lilliputian;

  use Moose;
  extends 'Person';

  sub _build_size { return 'small' }

 

Builder配合role工作

由于builders都是通过函数名字来调用的,所以builders和role是可以结合起来使用的, 例如,一个role(你可以理解为类似java接口类似的东西,后面的manual会有更多的介绍)来指定builder

  package HasSize;
  use Moose::Role;

  requires '_build_size';

  has 'size' => (
      is      => 'ro',
      lazy    => 1,
      builder => '_build_size',
  );

  package Lilliputian;
  use Moose;

  with 'HasSize';

  sub _build_size { return 'small' }

可以看到后面的代码,在Lilliputian中指定了HasSize Role中的_build_size方法.

 

懒惰lazy特性

之前我们已经提到了lazy 的设置

  has 'size' => (
      is      => 'ro',
      lazy    => 1,
      builder => '_build_size',
  );

当lazy => 1时候,默认的builder只有当对应的read访问器被调用的时候,_build_size才会被调用. 这样做是非常有优点的:

首先,如果默认值是决定于其他属性的,那么这个size设置肯定是要滞留到最后才能调用,因为因果关系.

其次,我们没有任何理由在我们不需要一个default value的时候去调用一个builder,只有我们需要的时候, 我们才会去调用.

我们强烈推荐你这样做,不仅高效而且可读性好,当然你可能觉得有点麻烦(至少哥这么觉得).

 

构造参数

一般来说,我们创建一个对象需要一个构造函数,至少C++里面是如此,perl5里面也需要我们定义一个 new方法,虽然这个new的命名并不是唯一的,就像我们创建一个DBI对象并不是使用new,而是通过

    DBI->connect( $dsn,$user,$pass )

但是moose里面我们并不需要写new方法,默认Moose会为你创建一个new方法来构造一个对象.当然,对应的你 需要使用has来指定属性,和perl5没什么区别,该传递参数还是得传递参数,我们来看一下moose是如何传递参数 给构造器的,因为并不如perl5 oop的那样简单.

has 'bigness' => (
is => 'ro',
init_arg => 'size',
);

现在我们有一个'bigness'的attribute,但是我们传递的参数确是size,这也就是说你想要初始化bigness这个属性,必须通过size 来指定.

 

这样做的好处就是禁止了对该属性的更改,如果你把init_arg => undef,那么你将不能设置该属性.

  has '_genetic_code' => (
      is       => 'ro',
      lazy     => 1,
      builder  => '_build_genetic_code',
      init_arg => undef,
  );

 

循环引用

Moose内置是支持weak reference的,如果你设置 weak_ref => 1,

  has 'parent' => (
      is       => 'rw',
      weak_ref => 1,
  );

  $node->parent($parent_node);

如果你创建的对象包含有循环引用并且你不想他因此而崩溃掉,使用weak_ref是非常有效的,Moose会为你处理好一切.

 

触发器

trigger是一个函数,它在属性被设置的时候被调用,有点callback的调调,

  has 'size' => (
      is      => 'rw',
      trigger => \&_size_set,
  );

  sub _size_set {
      my ( $self, $size, $old_size ) = @_;

      my $msg = $self->name;

      if ( @_ > 2 ) {
          $msg .= " - old size was $old_size";
      }

      $msg .= " - size is now $size";
      warn $msg;
  }

当该属性被设置,_size_set即被调用,并且接受new和old值作为参数,如果该属性之前没有被设置过, 则old值传递为undef.

 

属性类型

属性的类型,一旦被设定,对象构造的时候就仅接受该类型的数据,(是不是很兴奋,perl oop终于支持类型了)

  has 'first_name' => (
      is  => 'ro',
      isa => 'Str',
  );

上面的代码表示first_name必须为字符串.

Moose还提供了一个快捷的方法指定一个attribute的类型,就是通过一个role来实现, 我们使用关键字does来指定

  has 'weapon' => (
      is   => 'rw',
      does => 'MyApp::Weapon',
  );

 

团体

一个attribute可以定义delegate,(你可以看做一个包含了很多东西的团体),

  has 'hair_color' => (
      is      => 'ro',
      isa     => 'Graphics::Color::RGB',
      handles => { hair_color_hex => 'as_hex_string' },
  );

实际上是调用了 $self->hair_color->as_hex_string;

 

属性特性和元类

Moose一个最好的特点,你可以通过traits和metaclass来实现自省(这里我暂且剽窃一下python的东西吧)

你可以提供一个或者过个traits给一个attribute来描述属性

  use MooseX::MetaDescription;

  has 'size' => (
      is          => 'ro',
      traits      => ['MooseX::MetaDescription::Meta::Trait'],
      description => {
          html_widget  => 'text_input',
          serialize_as => 'element',
      },
  );

CPAN还有很多这样的模块,更详细的内容请参看Moose::Manual::MooseX.

 

定义自己的数据团体

Native delegations允许你定义perl的数据结构.

比如,你需要一个数组的引用有push,shif,map,count的方法

  has 'options' => (
      traits  => ['Array'],
      is      => 'ro',
      isa     => 'ArrayRef[Str]',
      default => sub { [] },
      handles => {
          all_options    => 'elements',
          add_option     => 'push',
          map_options    => 'map',
          option_count   => 'count',
          sorted_options => 'sort',
      },
  );

请参考 the Moose::Manual::Delegation manpage

 


属性继承

属性继承

  package LazyPerson;

  use Moose;

  extends 'Person';

  has '+first_name' => (
      lazy    => 1,
      default => 'Bill',
  );

如果你想继承一个属性,使用'+attribute_name',如上的代码,first_name现在增加了一个lazy的属性.

但是我们推荐你再你不够熟练之前,不要装逼的这么使用.

 


多属性快捷创建

Moose可以让你一次设置多个属性,就像我们可以一次定义多个变量一样

  package Point;

  use Moose;

  has [ 'x', 'y' ] => ( is => 'ro', isa => 'Int' );
  for my $name ( qw( x y ) ) {
      my $builder = '_build_' . $name;
      has $name => ( is => 'ro', isa => 'Int', builder => $builder );
  }

 


更多

Moose attribute是一个非常大的范畴,这里只是给你放了一遍幻灯片,我们推荐你多看看the Moose::Manual::Delegation manpagethe Moose::Manual::Types manpage 你会理解的更透彻.

 


更多的信息

Moose还有很多其他的关于attribute的设置,下面列出来的是一些你可能感兴趣的.

 

文档描述

你可以提供一段doc为你的attribute.

  has 'first_name' => (
      is            => 'rw',
      documentation => q{The person's first (personal) name},
  );

Moose对此并无上面处理,只是存储了这段doccument.

 

序列化数据

如果你的attribute是一段数组引用或者hash引用,auto_deref 这个设置会让你使用 reader来访问这个属性的时候,可以看到序列化以后的内容.

  my %map = $object->mapping;

当然这仅仅在你的attribute 是arrayref和hashref的时候.

尽管如此,我们仍然推荐你多看看the Moose::Meta::Attribute::Native manpage

 

初始化

Moose提供了一个attribute option 叫做initializer. 类似我们perl5 oop的new函数里面 会有类似的函数,Moose里面也是在构造对象的时候调用这个initializer.

 


作者

Stevan Little <;stevan@iinteractive.com> 译者: 斯文牛氓 QQ:492003149

 


版权

This software is copyright (c) 2011 by Infinity Interactive, Inc.. PerlChina 官方交流群作品

 


备注

本文由个人根据cpan moose manual翻译,可能由于水平有限,还是有些不够明朗的地方,望读者见谅,不屑一顾者请绕道远行, 希望能成为学习moose的一个引导吧,英文好的请直接看原著.

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