業務メモ。
Class::Accessor::Fast を使って書かれたモジュール群を Moose を使って書き換え中。。
使い始めてまだ3日くらい。使いこなせるようにがんばる。
Mooseについて
Mooseは Meta Object Protocol (MOP) という概念を採用したオブジェクト定義の技法を提供する。
(Class::MOP のラッパー)
Webアプリケーションフレームワークの Catalyst も5.8系からMooseベースになったとのこと。
型制約
Mooseで指定したアトリビュートは、生成されたアクセサで値を設定する際にバリデーションを走らせることができ、has に渡す isa 引数に型を指定することによってトリガーされる。以下は組み込みで定義されている型制約の図。Maybe[‘a] は、例えば Maybe[Int] と指定した場合、Int または undef として扱われる。
遅延評価
必要な時だけ値が計算されるので計算量を低減できる。アトリビュートの初期化において、複数の値から1つのオブジェクトを生成する時などに利用する。has の lazy_build フラグをセットすると、初期化時に_build_”アトリビュート名” という名前のメソッドを自動的に探索して実行する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
package MyServer; use Moose; # strict, warnings プラグマは自動的に有効になる use IO::Socket; has address => ( is => 'rw', # getter/setter アクセサを生成 (※ getterのみ作る場合は 'ro' と指定) isa => 'Str', # 型制約、文字列 required => 1, # 必須パラメータ指定 ); has port => ( is => 'rw', isa => 'Int', # 型制約、整数 required => 1, default => 1234, # デフォルト値 ); has server_socket => ( is => 'rw', isa => 'IO::Socket', lazy_build => 1, # 遅延評価指定 ); # アトリビュート初期化を遅延評価 # 初めて値が利用されるときに実行される sub _build_server_socket { my $self = shift; IO::Socket::INET->new( Listen => 5, LocalAddr => $self->address, LocalPort => $self->port, Proto => 'tcp', ); } __PACKAGE__->meta->make_immutable; # メタクラスを不変化 (高速化のため) no Moose; # Moose をアンインポート (外部から has などを使われないようにするため) 1; |
・確認
1 2 3 4 5 6 7 8 9 10 11 12 |
use strict; use warnings; use Data::Dumper; use MyServer; eval { my $server = MyServer->new(address => '127.0.0.1'); print Dumper($server); $server->server_socket; # 透過的に _build_server_socket が実行される print Dumper($server); }; print Dumper($@) if $@; |
・出力結果
1 2 3 4 5 6 7 8 9 |
$VAR1 = bless( { 'address' => '127.0.0.1', 'port' => 1234 }, 'MyServer' ); $VAR1 = bless( { 'address' => '127.0.0.1', 'port' => 1234, 'server_socket' => bless( \*Symbol::GEN0, 'IO::Socket::INET' ) }, 'MyServer' ); |
Role, Traits
Role を使うと機能(API)中心の設計ができるようになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# Role の定義方法 package Foo; use Moose::Role; requires 'create'; # requires で機能(API名)を定義 no Moose::Role; 1; # ---------- # Role の利用方法 package Bar; use Moose; with 'Foo'; # with で Role を指定 # create を実装しなければならない sub create { ... } ... |
また、Moose 0.89_01以降には、ネイティブトレート (native traits)が組み込まれている。
(以前のバージョンでは、MooseX::AttributeHelpers の metaclass などを使って実現)
以下は、Role, traits を使ったオブザーバパターンの実装例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# ----- Subject 抽象クラス package Foo::Subject; use Moose::Role; use Foo::Observer; has observers => ( traits => [qw/Array/], # 配列用ネイティブトレート is => 'rw', # getter/setter アクセサを生成 isa => 'ArrayRef[Foo::Observer], # 型制約、配列リファレンス default => sub { [] }, # デフォルト値、空の配列リファレンス handles => { add_observer => 'push', # ヘルパートレート 'push' に処理を委譲 } ); no Moose::Role; sub notify { my ($self, $event) = @_; foreach my $observer (@{$self->observers}) { $observer->update($event); } } 1; # ----- Observer インタフェース package Foo::Observer; use Moose::Role; requires 'update'; no Moose::Role; 1; # ----- Subject 実装 package Foo::ConcreteSubject; use Moose; with 'Foo::Subject'; # Rubyのmix-inのように使用できる has state => ( is => 'rw', isa => 'Int', default => 0, trigger => sub { # 値の変化をトリガーとして Foo::Subject->notify を呼びだす my $self = shift; $self->notify($self->state); } ); __PACKAGE__->meta->make_immutable; no Moose; 1; # ----- Observer 実装 package Foo::ConcreteObserverA; use Moose; with 'Foo::Observer'; __PACKAGE__->meta->make_immutable; no Moose; sub update { my ($self, $value) = @_; ... value に対応する処理 } 1; # ----- Observer 実装 package Foo::ConcreteObserverB; use Moose; with 'Foo::Observer'; __PACKAGE__->meta->make_immutable; no Moose; sub update { my ($self, $value) = @_; ... value に対応する処理 } 1; |
・クライアント
1 2 3 4 5 6 7 8 9 10 |
my $notifier = Foo::ConcreateSubject->new; # 各オブザーバの登録 $notifier->add_observer(Foo::ConcreteObserverA->new); $notifier->add_observer(Foo::ConcreteObserverB->new); # 内部で Foo::Subject->notify を呼び出して各オブザーバに値を通知 # trigger を設定しない場合は、 # $notifier->state(10); # $notifier->notify($notifier->state); # と同じ $notifier->state(10); |
Role, traits を使うことでこんな感じで簡潔に記述できる。また、アトリビュートやサブルーチンが記述された Role を with したクラス内では、その Role のアトリビュートにアクセスでき、サブルーチンも呼び出すことができる (Rubyのmix-inのような使い方)。
他の機能についてもちょっとずつ調べて追記する予定。