The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Book::Chinese::MasterPerlToday::Moose - Moose, OO

DESCRIPTION

Perl 的面对对象系统很强大(Moose 是由 Perl 编写),但并不是很好使用。建议使用 Moose

学习 Moose 最好的方法是阅读 Moose::Manual

对象系统

如果没有 Moose,Perl5 的 OO 应该是比较繁琐的。

  package Person;

  use strict;
  use warnings;

  sub new {
      my $class = shift;
      my %p = ref $_[0] ? %{ $_[0] } : @_;
      
      return bless \%p, $class;
  }

基本上你在写一些无用的东西,没有任何一行代码是跟实际应用的代码有关的。bless 是什么?这样的代码让人很迷惑。

Moose 的意义在于它可以让你专注于写什么(what),而不是怎么写(how)。

  package Person;
  
  use Moose;

概念

  • Class 类

    简单的讲,所以调用 use Moose 的包(package)都可以看做一个类。

    更简单的,一个类是由属性和方法组成。还有 Role, Constructor 和 Destructor 等。

  • Attribute 属性

    属性是一个类所拥有的性质。比如人都有一个名字。

    你可以简单的认为是类似 $self->{something} 的东西。

    一个属性最基本的用途是可读写。CPAN 上拥有上百个创建 Accessor 的模块。其中包括最流行的 Class::AccessorClass::Accessor::FastMoose 不是最快的(Class::XSAccessor 目前来说最快),但却是最强大的。

    Moose 的属性还拥有 delegation, lazy, clearer, predicate 等功能。

  • Method 方法

    方法与其他地方相同,由 sub 关键字定义。方法与属性最大区别是它往往是动词而属性一般是名词。

  • Role

    Role 与 use base 或者 extends 不同。with 'Role' 类似于将 Role 里的整个代码复制到 with 的模块里。它不显示在继承结构里。

  • Method modifiers

    方法修改器主要用于修改父类的方法。可以看做同类的有 SUPER, NEXTClass::C3

  • Type

    类型主要用于变量检测。

  • Constructor & Destructor

    原来的 Constructor 由 sub new 和 bless 组成。Moose 为你编写了一个 new, 如果你需要在初始化里做什么的话,可以编写 BUILD, 该方法会在 new 后调用。或者如果你想支持对 new 进来的参数做更改的话,使用 BUILDARGS.

    原来的 Destructor 由 DESTROY 控制。Moose 中一般使用 DEMOLISH

    查看 Moose::Object 源码可获得内部解释。

  • META

    meta 是描述一个类的类,由 Class::MOP 提供。

    MooseClass::MOP 的基础上提供了更多的功能。

    通过 meta, 你可以得到一个包的内在细节,包括它的父类,所有属性方法等。不仅仅如此,你还可以动态增减属性方法,修改父类等等。

细节例子

  • no Moose 和 immutabilize

      package Person;
    
      use Moose;
    
      # extends, roles, attributes, etc.
      # methods
    
      no Moose;
      __PACKAGE__->meta->make_immutable;
    
      1;

    我把这个写在最前面是因为这是当你写完 use Moose; 之后就应该马上要写下来的。

    还有

        no Moose::Util::TypeConstraints;
        no Moose::Role;
  • default 和 builder

    default 和 builder 都用于设置一个属性的初始化变量。

    如果值是一个非引用的话,建议使用 default

        default => 'apple'

    当值是引用时,必须放在 sub 里

        default => {}, # Wrong
        default => sub { {} } # Right, 默认空数组 sub { [] }

    一般在匿名子程序时,推荐用 builder

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

    因为 builder 所调用的是一个方法,所以拥有两大好处。一是方法可以在子类中被覆盖,另外方法还可以由 Role 提供。具有更好的可控和扩展性。

  • handles

    delegation 委托,委派。你可以将你自己所需要写的一些 subs 委托给其他的模块来处理,这样外部看起来就像这些 subs 就是属于这个包的。

        package A;
        
        use Moose;
        has 'ua' => (
            is  => 'rw',
            isa => 'LWP::UserAgent',
            lazy => 1,
            default => sub {
                LWP::UserAgent->new
            },
            handles => ['get', 'post', 'head', 'request']
        );

    使用 handles 后,A->new 之后可以直接调用 get, post 等方法。 A->new->get 等同于 A->new->ua->get

    handles 接受 ARRAY | HASH | REGEXP | ROLE | DUCKTYPE | CODE, 上面的例子是 ARRAY。

    HASH 可以让你使用不同的 sub 名字。比如

        handles => {
            get_url => 'get'
        }

    这种用法在当你的模块里已经有一个 get 的时候就非常有用。sub get 是你原来的 get, 而 A->new->get_url = A->new->ua->get

    其他的诸如 REGEXP, ROLE, CODE, DUCKTYPE 都有点高级,在有需要的时候可以去 moose@perl.org 或者 irc.perl.org #moose 咨询。

  • init_arg

      package AAAA;
      use Moose;
      has 'bigness' => (
          is       => 'ro',
          init_arg => 'size',
      );

    初始化的时候就要用 AAAA->new( size => 1 );

    如果你不允许一个变量在 new 里被初始化,可以参考如下:

      has '_genetic_code' => (
          is         => 'ro',
          lazy_build => 1,
          init_arg   => undef,
      );
  • around, before 和 after

    一般如果只是简单的修改,我们可以使用 beforeafter, 但是这两个拥有很大的限制性。

    第一,他们两个不能修改 @_, 第二,他们不能中途退出(不能用 return 停止调用,除非 die),第三是他们的返回值会被忽略掉。

    如果遇到以上三种情况,可以使用 around

    具体的代码解释可以参考 Class::MOP::Method::Wrapped

  • inner 和 augment, override 和 super

    inneraugment 一般用于基础的父类定义通用代码,而继承的子类定义不同的代码。

      package Document;
      use Moose;
      sub as_xml {
          my $self = shift;
    
          my $xml = "<document>\n";
          $xml .= inner();
          $xml .= "</document>\n";
    
          return $xml;
      }
      
      package Report;
      use Moose;
      extends 'Document';
      augment 'as_xml' => sub {
          my $self = shift;
    
          my $xml = "<report>\n";
          $xml .= inner();
          $xml .= "</report>\n";
    
          return $xml;
      };
      
      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;
      };

    可以在 argment 里再次调用 inner.

    overridesuper 与普通的 $self->SUPER::display_name 并无特别大的不同。

    参考 Moose::Manual::MethodModifiers

  • Role exclude/alias

    当你使用两个或多个 Role 的时候,因为 Role 是平行无序的进入主模块的,所以如果有两个 Role 有相同的方法名字的时候,主模块就不知道采用哪个 Role 的方法。

    这个时候,如果某个 Role 里的方法有用而另一个方法没用的时候,我们一般用 exclude

        with 'Net::GitHub::V2::NoRepo' => { excludes => [ 'args_to_pass' ] };;

    如果两个 Role 的方法都有用的时候,一般用 alias

      with 'Breakable'   => { alias => { break => 'break_bone' } },
           'Breakdancer' => { alias => { break => 'break_dance' } };
  • MooseX::Types

    一般建议使用 MooseX::Types 而不是内置的 Moose Type。

    Moose Type 是全局类型,当你使用多个外部包的时候就有可能会造成类型冲突(除非所有的外部包都是用各自的 'packageA.name.' 作为前缀)。MooseX::Types 是属于当前包变量的类型,不容易冲突。

    Moose Type 是放在引号之内如 'Int',这种检测是在运行时检测,出错比较难发现。而 MooseX::Types 是不放在引号里的(如 Int),这种在编译时就会检测。

    MooseX::Types 拥有更多类型,更容易扩展。常见的如 MooseX::Types::Path::Class

  • Roles 和 Traits

    Traits 其实就是 Role。它们的区别在于:

    • Traits 可以拥有一个短名字

    • 通常在编译时使用的是 Role,在运行时使用的是 Trait

      这意味这 Role 是影响了整个 Class,而 Trait 仅仅影响了 Class 的某个实例(instance)

    如果你的模块允许用户配置来使用哪些 traits 的话,一般参考 MooseX::Traits

MooseX

Moose 所开发的软件

但你在写一个小脚本的时候,你可能不会觉得 Moose 怎么有用。但当你写一个很大的系统的时候,你会由衷地为自己选择 Moose 而感到幸运。

  • Catalyst, Reaction

    Catalyst 应当是使用 Moose 最广的例子。而 Reaction 应该是最复杂的例子。

  • Dist::Zilla

    Dist::Zilla 拥有一个与 MooseX::Object::Pluggable 不同的写 Plugins 的技巧。

      $_->before_build for $self->plugins_with(-BeforeBuild)->flatten;
      
      $_->gather_files    for $self->plugins_with(-FileGatherer)->flatten;
      $_->prune_files     for $self->plugins_with(-FilePruner)->flatten;
      $_->munge_files     for $self->plugins_with(-FileMunger)->flatten;
      $_->setup_installer for $self->plugins_with(-InstallTool)->flatten;
      
      $_->after_build({ build_root => $build_root })
        for $self->plugins_with(-AfterBuild)->flatten;

    代码使用不同的 Role 来依次执行 Plugins,简洁而有效。

  • KiokuDB

  • TryCatch

  • More

    http://cpants.perl.org/dist/used_by/Moose

    不要因为担心 Moose 的启动速度而放弃它,这点速度是绝对物有所值的。

资源

SEE ALSO

Moose, Moose::Manual

AUTHOR

Fayland Lam, <fayland at gmail.com>

COPYRIGHT & LICENSE

Copyright (c) 2009 Fayland Lam

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.