=encoding utf8 =pod =head1 題名 Moose::Manual::Attributes - Mooseによるオブジェクトのアトリビュート =head1 はじめに Mooseのアトリビュートには多くのプロパティがあります。アトリビュートはおそらくMooseの機能としてはずば抜けて強力で柔軟なものですし、アトリビュートを宣言するだけで強力なクラスを作れます。実際、アトリビュートの宣言しかないクラスを作ることもできます。 アトリビュートは、あるクラスに属するすべてのメンバーが持つプロパティです。たとえば、「Cオブジェクトにはかならず姓と名がある」といえます。また、アトリビュートは省略可能にすることもできるので、「Cオブジェクトの中には社会保障番号を持つものもある(持たないものもある)」ということもできます。 もっとも単純な例では、アトリビュートは(ハッシュのように)読み書き可能な名前付きの値とも考えられます。ただし、アトリビュートの場合はデフォルト値や型制約があったり、委譲などを行うこともできます。 アトリビュートは、ほかの言語ではスロットやプロパティと呼ばれることもあります。 =head1 アトリビュートのオプション アトリビュートを宣言するときにはC関数を使います。 package Person; use Moose; has 'first_name' => ( is => 'rw' ); これは、Cオブジェクトはすべて省略可能で読み書きもできる「first_name」というアトリビュートを持つ、ということです。 =head2 読み書き可能と読み取り専用 アトリビュートのプロパティはCに渡したオプションによって定義されます。オプションにはさまざまなものがありますが、もっともシンプルな例では、設定する必要があるのはCだけです。これはC (read-write)またはC (read-only)の値を取ります。Cなアトリビュートはアクセッサーに値を渡すことで保持されている値を変更することができますが、Cの場合はアクセッサを使って現在の値を読み込むことしかできません。 実際にはCすら省略できますが、そうすると生成されたアトリビュートにはアクセサがなくなり、Cなどの他のオプションと組み合わせて便利な使い方もできます。しかし、アトリビュートが(Cなどが生成するものも含めて)一個もアクセサを生成しない場合Mooseはプログラマーが何かを書き忘れた場合と判断し、警告を発します。もし本当に一個もアクセサを生成しない場合はCオプションにCと指定することでこの警告を止めることができます。 =head2 アクセサメソッド オブジェクトのアトリビュートにはそれぞれひとつ以上のアクセサメソッドがあります。アクセサというのはそのアトリビュートの値を読み書きするためのものです。 デフォルトではアクセサメソッドはアトリビュートと同じ名前になります。アトリビュートがCであると宣言した場合はアクセサも読み取り専用になり、読み書き用と宣言した場合は読み書き用のアクセサが得られます。簡単ですね。 先ほどのCの例でいうと、これで私たちはCオブジェクトのCアトリビュートの値を読み書きできるCアクセサをひとつ用意できた、ということになります。 お望みであれば、アトリビュートの値を読み書きするのに使うメソッド名を明示的に指定することもできます。これは特にアトリビュートの読み取りはパブリックにしたいけれど書き込みはプライベートにしたい場合に便利です。一例をあげましょう。 has 'weight' => ( is => 'ro', writer => '_set_weight', ); weightがほかのメソッドに基づいて計算される場合はこうしておいた方が便利かもしれません。Cメソッドが呼ばれるたびにweightを修正するようなことがあるかもしれないからです。こうしておけば、weightの値を変更する実装の詳細は隠したまま、クラスを使う人にweightの値を提供できるようになります。 また、読み取り用のメソッド名と書き込み用のメソッド名は区別したいという人もいるかもしれません。ダミアン・コンウェイはIの中で読み取り用のメソッドは「get_」で、書き込み用のメソッドは「set_」で始めることを推奨しています。 これはCメソッドとCメソッドの双方にメソッド名を指定すればまさにその通りのことができます。 has 'weight' => ( is => 'rw', reader => 'get_weight', writer => 'set_weight', ); こんなことを何度も繰り返すのは頭がおかしくなるほど退屈な作業だと思った方、正解です! ありがたいことに、Mooseには強力な拡張システムがありますから、デフォルトの命名規則をオーバーライドすることもできます。詳しくはLをご覧ください。 =head2 断定用のメソッド(predicate)とクリア用のメソッド(clearer) Mooseを使うと、偽または未定義値が入っているアトリビュートと、値がセットされていないアトリビュートとを明示的に区別できます。この情報を利用するにはアトリビュートにクリア用のメソッドと断定用のメソッドを定義する必要があります。 断定用のメソッドはあるアトリビュートにいま値がセットされているかどうかを返すものです。なお、アトリビュートに明示的にC、またはほかの偽値をセットした場合でも断定用のメソッドは真値を返しますのでご注意ください。 クリア用のメソッドはアトリビュートの値をセットされていない状態にするものです。これはC値をセットするのと同じ「ではありません」。ただし、これを区別できるのは断定用のメソッドを定義したときのみです! ここでアクセサと断定用のメソッド、クリア用のメソッドの関係を示すコードを見てみましょう。 package Person; use Moose; has 'ssn' => ( is => 'rw', clearer => 'clear_ssn', predicate => 'has_ssn', ); ... my $person = Person->new(); $person->has_ssn; # false $person->ssn(undef); $person->ssn; # returns undef $person->has_ssn; # true $person->clear_ssn; $person->ssn; # returns undef $person->has_ssn; # false $person->ssn('123-45-6789'); $person->ssn; # returns '123-45-6789' $person->has_ssn; # true my $person2 = Person->new( ssn => '111-22-3333'); $person2->has_ssn; # true デフォルトでは、Mooseは断定用のメソッドやクリア用のメソッドは用意してくれません。自分で明示的にメソッド名を指定する必要があります。 =head2 必須かどうか デフォルトでは、アトリビュートはすべて省略可能です。また、オブジェクトの生成時に用意されている必要もありません。アトリビュートを必須にしたければCオプションを真にするだけで結構です。 has 'name' => ( is => 'ro', required => 1, ); ただし、この「required」が実際に何を意味しているかについては何点か注意しておいた方がよいでしょう。 基本的に、requiredの意味は、このアトリビュート(C)はコンストラクタに渡す必要がある、ということだけです。値については何も指定していませんから、Cということもありえます。 また、たとえ必須アトリビュートであってもクリア用のメソッドが定義されていれば値をクリアすることはできるので、オブジェクト生成後は値がセットされていない、ということもありえます。 つまり、本当にアトリビュートを必須にしたいのであれば、クリア用のメソッドを定義するのはあまり意味がありません。ただし、場合によっては必須アトリビュートに「プライベートの」CやCを用意しておくのは便利なこともあります。 =head2 デフォルト値とビルダーメソッド アトリビュートにはデフォルト値を持たせることもできます。Mooseでデフォルト値を指定するには2通りの方法があります。 もっとも簡単なのは、Cオプションに単にリファレンスではないスカラ値を渡す方法です。 has 'size' => ( is => 'ro', default => 'medium', predicate => 'has_size', ); コンストラクタにsizeアトリビュートが渡されなかった場合、その値はCになります。 my $person = Person->new(); $person->size; # medium $person->has_size; # true また、Cにサブルーチンのリファレンスを渡すこともできます。このリファレンスはオブジェクトのメソッドという形で呼ばれます。 has 'size' => ( is => 'ro', default => sub { ( 'small', 'medium', 'large' )[ int( rand 3 ) ] }, predicate => 'has_size', ); くだらない例ですが、新しいオブジェクトを生成するたびにこのサブルーチンが呼ばれるというポイントはおわかりでしょうか。 Cのサブルーチンリファレンスを渡した場合は、追加のパラメータなしのオブジェクトメソッドとして呼ばれます。 has 'size' => ( is => 'ro', default => sub { my $self = shift; return $self->height > 200 ? 'big' : 'average'; }, ); オブジェクト生成時にCが呼ばれても、ほかのアトリビュートにはまだ値がセットされていないこともあります。アトリビュートのデフォルト値がそのオブジェクトのほかの状態に依存している場合は、そのアトリビュートをCにできます。遅延評価については次のセクションで扱います。 デフォルト値に何らかのリファレンスを渡したい場合は、サブルーチンの返り値として渡す必要があります。そうしないと、Perlはそのリファレンスを1回だけインスタンス化したあと、それをすべてのオブジェクトで使い回してしまうためです。 has 'mapping' => ( is => 'ro', default => {}, # wrong! ); Mooseはデフォルト値にサブルーチンリファレンス以外の裸のリファレンスを渡した場合はエラーが発生します。 これは、裸のリファレンスを渡せるようにしてしまうと、デフォルトでマッピングされるアトリビュートが多くのオブジェクトで共有されてしまうことになりがちだからです。だから、裸のリファレンスを渡すのではなく、サブルーチンリファレンスでくるんでください。 has 'mapping' => ( is => 'ro', default => sub { {} }, # right! ); ちょっとわかりづらいですが、これがPerlのやり方なのです。 また、サブルーチンリファレンスを使うかわりに、アトリビュートにCメソッドを定義する方法もあります。 has 'size' => ( is => 'ro', builder => '_build_size', predicate => 'has_size', ); sub _build_size { return ( 'small', 'medium', 'large' )[ int( rand 3 ) ]; } こちらにはいくつか利点があります。まず、コードのかたまりを独自の名前を持つメソッドに移せますから、読みやすくなりますし、コードもすっきりします。 だから、ごく単純なデフォルト値を渡すとき以外は、CのかわりにCを使うことを強くおすすめします。 Cも、Cと同じく追加のパラメータなしのオブジェクトメソッドとして呼ばれます。 =head3 ビルダーを使うとサブクラス化できるようになります Cは「名前で」呼ばれますから、Perlがメソッド解決を行います。つまり、builderメソッドは継承もオーバーライドもできるということです。 Cクラスをサブクラス化する場合、C<_build_size>もオーバーライドできます。 package Lilliputian; use Moose; extends 'Person'; sub _build_size { return 'small' } =head3 ビルダーはロールから合成することもできます builderは名前で呼ばれますから、ロールとも共存できます。たとえば、ロールの方でアトリビュートを提供して、Cは取り込む側のクラスに要求するということも可能です。 package HasSize; use Moose::Role; requires '_build_size'; has 'size' => ( is => 'ro', lazy => 1, builder => '_build_size', ); package Lilliputian; use Moose; with 'HasSize'; sub _build_size { return 'small' } ロールについてはLをご覧ください。 =head2 遅延評価とC アトリビュートをCにすると、アトリビュートの初期化を遅らせることができます。 has 'size' => ( is => 'ro', lazy => 1, builder => '_build_size', ); Cが真の場合、デフォルト値が生成されるのはオブジェクト生成時ではなく、はじめて読み取り用のメソッドが呼ばれたときになります。遅延評価した方がよい場合はいくつかあります。 まず、アトリビュートのデフォルト値がほかのアトリビュートに依存している場合です。この場合、アトリビュートは「かならず」Cにしなければなりません。オブジェクト生成時にはデフォルト値が予想通りの順便で生成されるとは限らないので、デフォルト生成時にほかのアトリビュートが初期化されていることを期待するわけにはいかないのです。 次に、必要になるまではデフォルトを生成する理由がない場合も多々あります。アトリビュートをCにしておけば、必要になるまでアトリビュートの生成コストはかかりません。そのアトリビュートが「一度も」必要とされなかった場合は、CPU時間をいくらか節約できます。 だから、当然ながらビルダーを使っていたり、デフォルト値が単純とはいえないアトリビュートについてはかならずCにすることをおすすめします。 設定を簡単にしたい場合は単にCというアトリビュートオプションを指定するだけでも結構です。こうするといくつかのオプションをまとめてくれます。 has 'size' => ( is => 'ro', lazy_build => 1, ); これは、以下のすべてのオプションを指定するのと同じ働きをします。 has 'size' => ( is => 'ro', lazy => 1, builder => '_build_size', clearer => 'clear_size', predicate => 'has_size', ); アトリビュート名がアンダースコア(C<_>)で始まる場合、クリア用のメソッドと断定用のメソッドもアンダースコアで始まります。 has '_size' => ( is => 'ro', lazy_build => 1, ); 上の例は、このようになります。 has '_size' => ( is => 'ro', lazy => 1, builder => '_build__size', clearer => '_clear_size', predicate => '_has_size', ); ビルダー名もアンダースコアが2つになっていることに注意してください。内部的には単にアトリビュート名の前に「_build_」をつけているだけです。 Cが生成する名前が気に入らない場合は、いつでも自分で名前を指定できます。 has 'size' => ( is => 'ro', lazy_build => 1, clearer => '_clear_size', ); 自分で明示的に指定したオプションはかならずMoose内部のデフォルトより優先されます。 =head2 コンストラクタのパラメータ (C) アトリビュートは、デフォルトではアトリビュート名をキーにしてクラスのコンストラクタに値を渡せるようになっていますが、場合によってはコンストラクタのパラメータとしては別の名前を使いたいとか、コンストラクタ経由ではアトリビュートに値をセットできないようにしたいこともあるかもしれません。 いずれの場合でも、これはCオプションを利用すれば実現できます。 has 'bigness' => ( is => 'ro', init_arg => 'size', ); これで「bigness」という名前がついているものの、コンストラクタに渡すときにはCという名前を使うアトリビュートができます。 もっと役に立つ例としては、コンストラクタ経由ではアトリビュートに値をセットできないようにできます。これはプライベートなアトリビュートの場合、特に便利です。 has '_genetic_code' => ( is => 'ro', lazy_build => 1, init_arg => undef, ); CをCにすると、新しいオブジェクトを生成するときにこのアトリビュートに値をセットすることはできなくなります。 =head2 ウィークリファレンス Mooseにはウィークリファレンスのサポートが組み込まれています。Cオプションを真値にすると、アトリビュートに値が書き込まれたときはかならずCを呼ぶようになります。 has 'parent' => ( is => 'rw', weak_ref => 1, ); $node->parent($parent_node); これは循環参照になるかもしれないオブジェクトを作るときには非常に便利です。 =head2 トリガ Cはアトリビュートに値がセットされたときにかならず呼ばれるサブルーチンです。 has 'size' => ( is => 'rw', trigger => \&_size_set, ); sub _size_set { my ( $self, $size, $old_size ) = @_; my $msg = $self->name; if ( @_ > 2 ) { $msg .= " - old size was $old_size"; } $msg .= " - size is now $size"; warn $msg; } トリガは新しい値がセットされた「あと」で呼び出されます。このトリガはメソッドとして呼び出され、新しい値と古い値を引数として呼び出されます。もしアトリビュートの値がその前に一回も設定されていない場合は、新しい値のみが渡されます。この特性を利用することにより、アトリビュートが新規に値を設定された時と、前の値がundefだった場合の違いを認識することができます。 これは、Cメソッドモディファイアとは2つの点で異なります。まず、トリガの方は、アクセサメソッドが呼ばれたときかならず(読み込んだときも書き込んだときも)呼ばれるのではなく、アトリビュートに値がセットされたときだけ呼ばれます。また、アトリビュートの値がコンストラクタに渡されたときにも呼ばれます。 ただし、トリガはCやCで値が初期化されたときには呼ばれ「ません」。 =head2 アトリビュートの型 アトリビュートは特定の型しか受け入れないように制限できます。 has 'first_name' => ( is => 'ro', isa => 'Str', ); こうすると、Cアトリビュートはかならず文字列でなければならなくなります。 また、アトリビュートが特定のロールを持つオブジェクトだけを受け入れるように指定するショートカットもあります。 has 'weapon' => ( is => 'rw', does => 'MyApp::Weapon', ); 型システムについての詳細はLのドキュメントをご覧ください。 =head2 委譲 アトリビュートの値に処理を委譲するだけのメソッドを定義することもできます。 has 'hair_color' => ( is => 'ro', isa => 'Graphics::Color::RGB', handles => { hair_color_hex => 'as_hex_string' }, ); こうすると、オブジェクトにCという新しいメソッドが追加されるのですが、Cを呼んでも、内部的にはただC<< $self->hair_color->as_hex_string >>を呼び出すだけです。 委譲メソッドの設定の仕方についてはLをご覧ください。 =head2 メタクラスとトレート Mooseでもっとも便利な機能のひとつに、独自のメタクラスやメタクラスのトレートを使えばあらゆる点で拡張できることがあげられます。 アトリビュートを宣言するときも、アトリビュートのメタクラスやトレートを宣言できます。 has 'mapping' => ( metaclass => 'Hash', is => 'ro', default => sub { {} }, ); この例でいうと、Cというメタクラスは、実際にはLを指します。この他にも L、L、L、L、Lのネイティブトレートを提供します。 アトリビュートにはひとつないし複数のトレートを組み込むこともできます。 use MooseX::MetaDescription; has 'size' => ( is => 'ro', traits => ['MooseX::MetaDescription::Meta::Trait'], description => { html_widget => 'text_input', serialize_as => 'element', }, ); トレートの利点は、複数のトレートを簡単に組み合わせられることです(トレートは、実はロールに皮をかぶせたものなのです)。 CPANにはアトリビュートに便利なメタクラスやトレートを提供するMooseXモジュールがいくつもあります(Lにいくつか例が紹介されています)。また、自前のメタクラスやトレートを書くこともできます。例についてはLの「メタMoose」および「Mooseを拡張する」以下に記載されているレシピをご覧ください。 =head1 アトリビュートの継承 デフォルトでは、子クラスは親(祖先)クラスのすべてのアトリビュートをそのまま継承します。ただし、子クラスの中で継承したアトリビュートの一部の特徴を明示的に変更することもできます。 サブクラスでオーバーライドできるオプションは次の通りです。 =over 4 =item * default =item * coerce =item * required =item * documentation =item * lazy =item * isa =item * handles =item * builder =item * metaclass =item * traits =back アトリビュートをオーバーライドするのは、アトリビュート名の前にプラス記号(C<+>)をつけるだけでできます。 package LazyPerson; use Moose; extends 'Person'; has '+first_name' => ( lazy => 1, default => 'Bill', ); これでCのCアトリビュートは遅延評価をするようになり、デフォルト値はC<'Bill'>になります。 継承したアトリビュートの型(C)を変える場合は慎重にすることをおすすめします。 =head1 複数アトリビュート定義時のショートカット 名前以外の定義が同じアトリビュートを複数定義する場合は、それらを一度に定義することができます: package Point; use Moose; has [ 'x', 'y' ] => ( is => 'ro', isa => 'Int' ); もちろん、Cはただの関数ですので、ループ内で呼べば同じ事ができます: for my $name ( qw( x y ) ) { my $builder = '_build_' . $name; has $name => ( is => 'ro', isa => 'Int', builder => $builder ); } =head1 もっと詳しく知りたい方は Mooseのアトリビュートは複雑なテーマです。このドキュメントではそのいくつかの側面をさらっと紹介したにすぎません。さらに理解を深めたい方は、LとLのドキュメントを読むことをおすすめします。 =head1 その他のオプション Mooseのアトリビュートには多くのオプションがあります。ここで紹介するものはよりモダンな機能で置き換えられてしまいましたが、完璧を期すために取り上げました。 =head2 Cオプション アトリビュートのドキュメントとなる文字列を指定できます。 has 'first_name' => ( is => 'rw', documentation => q{The person's first (personal) name}, ); Mooseがこの情報を利用することはいっさいありません。保存しておくだけです。 =head2 Cオプション アトリビュートが配列リファレンスまたはハッシュリファレンスの場合、Cオプションが有効になっていると、読み取り用のメソッドから値を返すときにデリファレンスするようになります。 my %map = $object->mapping; このオプションが有効なのはアトリビュートが明示的にCまたはCに型付けされている場合のみです。 ただし、そのようなアトリビュートには、さらにアトリビュートのアクセスや操作の幅が広がるLを利用することをおすすめします。 =head2 Cオプション MooseにはCというアトリビュートオプションがあります。これはCと同じようなものですが、呼ばれるのはオブジェクト生成時「のみ」というところが異なります。 このオプションはLから継承しているのですが、(Moose専用の) Cを使うことをおすすめします。 =head1 作者 Dave Rolsky Eautarch@urth.orgE =head1 COPYRIGHT AND LICENSE Copyright 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