取势 明道 优术

作者为 扶 凯 发表

概要

  package BinaryTree;
  use Moose;

  has 'node' => ( is => 'rw', isa => 'Any' );

  has 'parent' => (
      is        => 'rw',
      isa       => 'BinaryTree',
      predicate => 'has_parent',
      weak_ref  => 1,
  );

  has 'left' => (
      is        => 'rw',
      isa       => 'BinaryTree',
      predicate => 'has_left',
      lazy      => 1,
      default   => sub { BinaryTree->new( parent => $_[0] ) },
      trigger   => \&_set_parent_for_child
  );

  has 'right' => (
      is        => 'rw',
      isa       => 'BinaryTree',
      predicate => 'has_right',
      lazy      => 1,
      default   => sub { BinaryTree->new( parent => $_[0] ) },
      trigger   => \&_set_parent_for_child
  );
    sub _set_parent_for_child {
      my ( $self, $child ) = @_;

      confess "You cannot insert a tree which already has a parent"
          if $child->has_parent;

      $child->parent($self);
  }

描述

这个指南演示的是新的各种不同的高级属性(attribute)的特性,可以用来创建复杂和强大的行为.在这,我要介绍几个新的属性选项 predicate,lazy,trigger 这几个.
这个类的例子是一个典型的二叉树.树中的每个节点本身是一个二叉树.在这个节点中,包含着一些任意的值.他有右和左的属性,其中引用了子树和父.
让我们来看节点上的属性:

has 'node' => ( is => 'rw', isa => 'Any' );

Moose 产生成了读和写访问的属性和一个 Any 的类型约束.Any 的字面意思是它可以包含任何内容,实际也是.
我们不能漏掉 isa 的选项,因为在这种情况下,包含这个会对其它的程序员非常有益,而不是电脑.

 

接下来,让我们来看 parent 的属性.

has 'parent' => (
      is        => 'rw',
      isa       => 'BinaryTree',
      predicate => 'has_parent',
      weak_ref  => 1,
);

在次,我们这有一个读和写的访问的属性 rw.在这时,这个 isa 提示这个属性必须是 BinaryTree 的对象,因为这是一个类的类型约束 .在第二个指南中,我们见到每次创建基于 Moose 的类时会产生相同类名的类型约束.
这个 predicate 是一个新的属性相关的选项,它创建了一个可以用来检查给定的属性是否被初始化的方法.在这个地方,这个方法的名字是 has_parent.
这给我们带来了我们另一个属性相关的选项 weak_ref.由于 parent 是一个循环引用(在 parent 树应该已经有一个这样的引用,在其 left 或 right 属性),我们要确保削弱这个引用计数来避免内存泄漏,只要 weak_ref 是真.它就会改变了存取功能,设置这个使引用在这时削弱个引用计数.

最后,我有一个 left 和 right 属性.它们基本上是相同的,除了他们的名字,所以我们只要看 left:

has 'left' => (
      is        => 'rw',
      isa       => 'BinaryTree',
      predicate => 'has_left',
      lazy      => 1,
      default   => sub { BinaryTree->new( parent => $_[0] ) },
      trigger   => \&_set_parent_for_child
);

 

这又有三个新的选项 lazy, default 和 trigger .这个 lazy 和 default 选项是联系在一起的.其实,你并不能只有 lazy 属性,除非它是定义 default (或 builder,但我们稍后会介绍).如果你试着在没定义 default 时使用 lazy 的属性,类的创建就会 fail (2).
第二个指南在 BankAccount 类中的 balance 属性有一个 default 的默认值 0 .当 default 的设置不是一个引用时, Perl 会复制这个 default 设置的值.然而,当 default 设置的值是一个给定的引用时,它是不会深度克隆这个引用,而只是简单复制这个引用.如果你的 defined 只是一个简单引用的说明(匿名子函数引用),Perl 会创建它并会与有该属性的所有对象共享这个引用.
每次被调用时,默认我们会生成一个匿名子函数的新的引用.

has 'foo' => ( is => 'rw', default => sub { [] } );

事实上,如果使用非子函数的引用,在 Moose 中是默认认为不合法的.

# will fail
has 'foo' => ( is => 'rw', default => [] );

这样会出错,所以不要这样使用
你会注意到上面我们在 sub 中使用 $_[0] 来传参数 .所以当这个默认的子函数执行时,它会调用对象的方法.
就我们而言,我们正在创建我们默认的新二叉树对象,并设置当前树就是作为父.
注意,当对象被实例化,会对任何违反行为立即进行评估检查.在我们的二叉树的类中.这将是一个大问题!我们想创建第一个对象,它立即尝试填充其 left 和 right 属性,这将创建一个新的二叉树,又将填充其 left和 right. Kaboom !
使用 lazy 在我们的 left 和 right 属性上.我们才能避免这个问题.这时如果属性有一个只读的值时,默认是永远不会执行的检查.
这个行为还有一点,在这自动产生的 lift 和 right 的访问器不太正确的,因为当其中一个在设置,我们需要确保当其中一个设置时会更新左或右属性的树中的父.

 

我们能写我们自己的访问函数,但这为什么在这使用 Moose ? 反而,我们在这使用 trigger(触发器).触发器接受一个子函数的引用,每当这个属性设置时都会调用方法.这个生效在对象的构造时或在后面有新的对象通过属性访问方法.但是,它不会在 default 和 builder 提供的值为真时调用.

sub _set_parent_for_child {
      my ( $self, $child ) = @_;

      confess "You cannot insert a tree which already has a parent"
          if $child->has_parent;

      $child->parent($self);
}

这个触发器引用的函数会做二样事情.
首先.它会确保父(parent)是在新的子节点中是不存在的.这样做是为简化的例子缘故.如果我们想更为更灵活,我们最好从旧的父树中删除子,并将它添加一个新的树.
如果子中没有定义(parent).我们就给它加到当前树中.我们确保它在父属性中有一个对的值.
和其它全部的指南一样,BinaryTree 能使用其它的任何的 Perl5 的类.更多详细的内容看 t/recipes/moose_cookbook_basics_recipe3.t.

 

结论

这指南介绍了 Moose 的一些高级功能.我们希望,这能激励你去思考这些功能,用它去做可用于简化代码的地方.
(1) 弱引用是棘手的事情,应谨慎使用.如果你不小心,属性值可能会神秘消失,因为 Perl 的引用计数的垃圾收集和删除引用.总之,不要使用它们,除非你知道自己在做什么:)
(2)你可以使用默认的选项不使用 lazy 的选择,可以像我们展示了第二个指南.同样的,你可以使用 builder 而不是 default 属性.了解更多请看 Moose::Cookbook::Basics::Recipe8.

 

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



    lwp 2011年12月21日 的 15:16

    在这自动产生的 lift 和 right 的访问器不太正确的
    ==>
    错个单词