=encoding utf8 =pod =head1 題名 Moose::Cookbook::Meta::Recipe3 - アトリビュートのトレートを利用したラベルの実装 =head1 概要 package MyApp::Meta::Attribute::Trait::Labeled; use Moose::Role; has label => ( is => 'rw', isa => 'Str', predicate => 'has_label', ); package Moose::Meta::Attribute::Custom::Trait::Labeled; sub register_implementation {'MyApp::Meta::Attribute::Trait::Labeled'} package MyApp::Website; use Moose; has url => ( traits => [qw/Labeled/], is => 'rw', isa => 'Str', label => "The site's URL", ); has name => ( is => 'rw', isa => 'Str', ); sub dump { my $self = shift; my $dump = ''; my %attributes = %{ $self->meta->get_attribute_map }; for my $name ( sort keys %attributes ) { my $attribute = $attributes{$name}; if ( $attribute->does('MyApp::Meta::Attribute::Trait::Labeled') && $attribute->has_label ) { $dump .= $attribute->label; } else { $dump .= $name; } my $reader = $attribute->get_read_method; $dump .= ": " . $self->$reader . "\n"; } return $dump; } package main; my $app = MyApp::Website->new( url => "http://google.com", name => "Google" ); =head1 このレシピを読む前に このレシピはLのバリエーションです。まずはそちらを先にお読みください。 =head1 動機 Lではアトリビュートにラベルをつけられるアトリビュートメタクラスを作成しました。 でも、メタクラスを使うやり方が通用するのは、ラベル「と」有効期限のように、ほかの新しい振る舞いを組み合わせたくなるまでのこと。2つのメタクラスをサブクラス化する別のメタクラスを作る手もありますが、それをしては訳のわからないことになってしまいます(多くのアトリビュートにさまざまな振る舞いを組み合わせたい場合はなおさらです)。 さいわい、Mooseにはもっとまともな選択肢があります。それぞれの拡張モジュールをクラスではなくロールとしてカプセル化するというのがそれです。アトリビュートにラベルを追加するロールを作ることもできますし、もうひとつ有効期限を実装するロールを作ることもできるでしょう。 =head1 トレート メタクラスに組み込むロールにはトレートという特別な名前がついていますが、名前が違うからといってだまされないでください。B<トレートは単なるロールにすぎません>。 Lを使うとアトリビュートにCパラメータを渡せます。このパラメータはトレートのリストを受け取り、合成して無名のメタクラスを作り、その無名のメタクラスをアトリビュートのメタクラスにします。 そう、裏ではまだ大量のメタクラスを利用しているのですが、その管理はMooseの方でしてくれます。 トレートは、ロールができることなら何でもできます(アトリビュートを追加・改善したり、メソッドをラップしたり、メソッドを増やしたり、インタフェースを定義したり、等)。唯一違うのは、変更するのはユーザレベルのクラスではなく、アトリビュートのメタクラスである、ということだけです。 =head1 コードの詳細 このレシピとレシピ2のコード例を並べて見てみると、トレートの定義や利用の仕方は本格的なメタクラスの使い方とよく似ていることがわかります。 package MyApp::Meta::Attribute::Trait::Labeled; use Moose::Role; has label => ( is => 'rw', isa => 'Str', predicate => 'has_label', ); ここではLをサブクラス化するかわりにロールを定義します。Lのメタクラスと同様に、ロールを登録しておくと短い名前で参照できるようになります。 package Moose::Meta::Attribute::Custom::Trait::Labeled; sub register_implementation { 'MyApp::Meta::Attribute::Trait::Labeled' } Mooseがトレートのフルネームを調べるときはCのCメソッドを探します。 残りのコードについては、L<レシピ2|Moose::Cookbook::Meta::Recipe2>と「異なる」部分のみ見ていきましょう。 has url => ( traits => [qw/Labeled/], is => 'rw', isa => 'Str', label => "The site's URL", ); ここでは、Cパラメータを渡すかわりにCを渡しています。Cにはトレート名のリストが入ります。Mooseはこのトレートから構築した無名のアトリビュートメタクラスをそのアトリビュートのメタクラスとして利用します。C