業務メモ。
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_”アトリビュート名” という名前のメソッドを自動的に探索して実行する。
[perl]
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;
[/perl]
・確認
[perl]
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 $@;
[/perl]
・出力結果
$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)中心の設計ができるようになる。
[perl]
# 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 {
…
}
…
[/perl]
また、Moose 0.89_01以降には、ネイティブトレート (native traits)が組み込まれている。
(以前のバージョンでは、MooseX::AttributeHelpers の metaclass などを使って実現)
以下は、Role, traits を使ったオブザーバパターンの実装例。
[perl]
# —– 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;
[/perl]
・クライアント
[perl]
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);
[/perl]
Role, traits を使うことでこんな感じで簡潔に記述できる。また、アトリビュートやサブルーチンが記述された Role を with したクラス内では、その Role のアトリビュートにアクセスでき、サブルーチンも呼び出すことができる (Rubyのmix-inのような使い方)。
他の機能についてもちょっとずつ調べて追記する予定。