=encoding utf8 =pod =head1 題名 Moose::Cookbook::Basics::Recipe4 - サブタイプと、簡単なBクラス階層のモデリング =head1 概要 package Address; use Moose; use Moose::Util::TypeConstraints; use Locale::US; use Regexp::Common 'zip'; my $STATES = Locale::US->new; subtype 'USState' => as Str => where { ( exists $STATES->{code2state}{ uc($_) } || exists $STATES->{state2code}{ uc($_) } ); }; subtype 'USZipCode' => as Value => where { /^$RE{zip}{US}{-extended => 'allow'}$/; }; has 'street' => ( is => 'rw', isa => 'Str' ); has 'city' => ( is => 'rw', isa => 'Str' ); has 'state' => ( is => 'rw', isa => 'USState' ); has 'zip_code' => ( is => 'rw', isa => 'USZipCode' ); package Company; use Moose; use Moose::Util::TypeConstraints; has 'name' => ( is => 'rw', isa => 'Str', required => 1 ); has 'address' => ( is => 'rw', isa => 'Address' ); has 'employees' => ( is => 'rw', isa => 'ArrayRef[Employee]' ); sub BUILD { my ( $self, $params ) = @_; if ( @{ $self->employees || [] } ) { foreach my $employee ( @{ $self->employees } ) { $employee->employer($self); } } } after 'employees' => sub { my ( $self, $employees ) = @_; if ($employees) { foreach my $employee ( @{$employees} ) { $employee->employer($self); } } }; package Person; use Moose; has 'first_name' => ( is => 'rw', isa => 'Str', required => 1 ); has 'last_name' => ( is => 'rw', isa => 'Str', required => 1 ); has 'middle_initial' => ( is => 'rw', isa => 'Str', predicate => 'has_middle_initial' ); has 'address' => ( is => 'rw', isa => 'Address' ); sub full_name { my $self = shift; return $self->first_name . ( $self->has_middle_initial ? ' ' . $self->middle_initial . '. ' : ' ' ) . $self->last_name; } package Employee; use Moose; extends 'Person'; has 'title' => ( is => 'rw', isa => 'Str', required => 1 ); has 'employer' => ( is => 'rw', isa => 'Company', weak_ref => 1 ); override 'full_name' => sub { my $self = shift; super() . ', ' . $self->title; }; =head1 本文 このレシピではLが提供しているCというシュガー関数について紹介します。C関数を使うとクラスをひとつまるごと用意しなくても宣言的に型制約を生成できるようになります。 また、既存のCPANツールを使ってデータを検証する方法を紹介するため、LとLを使った制約も作成します。 最後に、Cというアトリビュートオプションを紹介します。 C
クラスでは2つのサブタイプを定義しています。ひとつめのサブタイプは、Lモジュールを使って州の名前を検証するものです。このサブタイプは、州の略称も正式名称も受け付けるようになっています。 州の名前は文字列として渡したいので、C型はMoose組み込みのC型のサブタイプにしました。これはCというシュガー関数を使って指定します。実際の制約はCを使って定義します(この関数はサブルーチンリファレンスをひとつ受け取ります)。このサブルーチンを呼ぶと、チェックしたい値がC<$_>に入ります(1)。また、返り値としてはその型として有効な値かどうかをあらわす真偽値が期待されています。 これでC型はMooseの組み込み型と同じように使えるようになりました。 has 'state' => ( is => 'rw', isa => 'USState' ); Cアトリビュートに値がセットされると、C型の制約を満たすかチェックされ、値が有効でなかった場合は例外が発生します。 次のCというサブタイプではLを使います。Lにはアメリカの郵便番号を検証する正規表現が含まれていますので、これをCアトリビュートの制約として利用します。 subtype 'USZipCode' => as Value => where { /^$RE{zip}{US}{-extended => 'allow'}$/; }; それぞれの型についてクラスを要求するかわりにサブタイプを使うと、コードが非常に簡潔になります。ここで取り上げた型の場合、値は単なる文字列ですから本当にクラスは必要ありません(ただし、有効な値かどうか確認したいのは本当です)。 ここで作成した型制約は再利用できます。型制約はグローバルなレジストリに名前付きで保存されるので、ほかのクラスからも参照できるのです。ただし、このレジストリはグローバルなので、実際のアプリケーションではCのようになんらかの擬似的な名前空間を利用することを強くおすすめします。 この2つのサブタイプを使うと、簡単なC
クラスを定義できます。 続いて、Cクラスを定義しましょう。このクラスには住所の情報を持たせます。これまでのレシピで見たように、Mooseはそれぞれのクラスに自動的に型制約を生成してくれるので、ここでもCクラスのC
アトリビュートにはその自動生成された型制約を利用します。 has 'address' => ( is => 'rw', isa => 'Address' ); また、会社には名前も必要です。 has 'name' => ( is => 'rw', isa => 'Str', required => 1 ); ここではCという新しいアトリビュートオプションが登場しました。アトリビュートが必須になっている場合、かならずクラスのコンストラクタにそのアトリビュートを渡さなければなりません(そうでない場合は例外が発生します)。ただし、これもぜひ理解しておいていただきたいのですが、Cアトリビュートは、型制約が許せば偽値やCであってもかまいません。 次のCアトリビュートでは「パラメータ付きの」型制約を利用しています。 has 'employees' => ( is => 'rw', isa => 'ArrayRef[Employee]' ); この制約の意味は、Cは配列リファレンスでなければならず、配列のそれぞれの要素はCオブジェクトでなければならない、ということ(「空の」配列リファレンスもこの制約を満たすことは注記しておくべきでしょう)。 Cのようなパラメータ指定可能な型制約(「コンテナ」型)は、型パラメータを使うとより具体的に書けますが(実際にはこれらの型を任意の回数ネストさせてCのような制約を作ることもできます)、指定された型を単独で使うだけでもかまいません(だから、Cという型制約も合法です)。(2) 途中を飛ばしてCクラスの定義を見ると、Cアトリビュートがあります。 CのCに値をセットしたときは、それぞれの従業員オブジェクトのCアトリビュートも確実に正しいCを参照させたいところです。 そのためにはオブジェクトの生成に割り込みをかける必要があります。Mooseでは、クラスにCメソッドを書くとそうすることができるようになります。定義しておいたCメソッドは、オブジェクトが生成された直後、呼び出し元にそのオブジェクトを返す前に呼ばれます。(3) CクラスではCメソッドを使って会社の各従業員がかならずCアトリビュートに適切なCオブジェクトを持つようにしています。 sub BUILD { my ( $self, $params ) = @_; if ( $self->employees ) { foreach my $employee ( @{ $self->employees } ) { $employee->employer($self); } } } このCメソッドは、型制約のチェックが済んだあとに実行されます。だから、C<< $self->employees >>が配列リファレンスを返すことや、その配列の要素がCオブジェクトであることは間違いないものと決めてかかっても大丈夫です。 また、CのCアトリビュートが変わったときもかならず各従業員のCを更新しておきたいところです。 そうするには、Cモディファイアが使えます。 after 'employees' => sub { my ( $self, $employees ) = @_; if ($employees) { foreach my $employee ( @{$employees} ) { $employee->employer($self); } } }; ここでもまた、Cメソッドと同じく事前に型制約のチェックが済んでいることはわかっていますので、C<$employees>引数が定義されているかどうかだけのチェックで済ませられます。 Bクラスには特に目新しいことはありません。Cなアトリビュートがいくつかあるのと、L<レシピ3|Moose::Cookbook::Basics::Recipe3>ではじめて使ったCメソッドがひとつあります。 Cクラスも、新しい機能はCメソッドモディファイアだけです。 override 'full_name' => sub { my $self = shift; super() . ', ' . $self->title; }; これはPerlに組み込まれているC機能のかわりをするシュガー関数に過ぎないのですが、ひとつ違うところがあります。Cには引数を渡せないのです(Mooseは単にメソッドに渡されたのと同じパラメータを渡します)。 Fにはもっと詳しい使用例があります。 =head1 まとめ このレシピはあえてやや長く、複雑なものにしました。ここではMooseのクラスと型制約を組み合わせて使う方法や、Mooseを使うとわずかなタイプ量でさまざまな情報を取得できるようになることを説明しました。 また、このレシピではC関数やCアトリビュート、Cメソッドモディファイアの使い方も紹介しました。 型制約については型変換ともどもこの先のレシピで再度取り上げます。 =head1 脚注 =over 4 =item (1) チェックしたい値はCブロックの最初の引数として渡されます(C<$_[0]>でアクセスできます)。 =item (2) ただし、Cは正しく動作しません。このような場合、Mooseはコンテナ型とはみなさず、「ArrayRef[]」という意味不明な新しい型が指定されたものとみなすためです。 =item (3) Cメソッドは、実際にはC<< Moose::Object->new >>が呼び出すC<< Moose::Object->BUILDALL >>から呼ばれます。Cメソッドは、オブジェクトの継承グラフをたどって、見つけたCメソッドをすべて、正しい順序で呼び出すものです。 =back =head1 作者 Stevan Little Estevan@iinteractive.comE Dave Rolsky Eautarch@urth.orgE =head1 COPYRIGHT AND LICENSE Copyright 2006-2009 by Infinity Interactive, Inc. L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut