Moose-0.92 > Moose::Manual::Concepts

題名

Moose::Manual::Concepts - Mooseによるオブジェクト指向のコンセプト

Mooseのコンセプト(と「従来の」Perl)

昔はパッケージとクラス、アトリビュートとメソッド、コンストラクタとメソッド等々の違いなんてあまり考えたことはなかったかもしれません。でも、Mooseの世界では、これらはすべて(たとえ裏では昔ながらのPerlで実装されているにしても)概念的には別々のものです。

私たちのメタオブジェクトプロトコル(MOP)はこれらの概念それぞれに対して明確に定義されたイントロスペクション機能を用意しています(Mooseはそのそれぞれに別のシュガー関数を用意しています)。また、Mooseはロールやメソッドモディファイア、宣言的な委譲といった概念も新たに導入しています。

Mooseの手ほどきとして、まずはこれらの概念がMoose語ではどういう意味になるのか、昔ながらのPerl 5のオブジェクト指向ではどのように使われてきたのかを調べてみましょう。

クラス

パッケージの中で「use Moose」すると、そのパッケージはクラスになります。もっともシンプルなクラスにはアトリビュートやメソッドしかありませんが、ロールやメソッドモディファイアなどを含めることもできます。

クラスには0個以上のアトリビュートがあります。

クラスには0個以上のメソッドがあります。

クラスには0個以上のスーパークラス(親クラス)があります。クラスはスーパークラスを継承します。

クラスには0個以上のメソッドモディファイアがあります。このモディファイアは、そのクラスのメソッドだけでなく、祖先から継承したメソッドにも適用できます。

クラスには0個以上のロールがあります(取り込めます)。

クラスにはコンストラクタデストラクタがあります。いずれもMooseが「ただで」提供してくれるものです。

コンストラクタはクラスのアトリビュートに対応した名前付きのパラメータを受け取り、それを使ってオブジェクトのインスタンスを初期化します。

クラスにはメタクラスがあります。メタクラスにはメタアトリビュートメタメソッドメタロールがあります。メタクラスはそのクラスを説明するものです。

クラスはふつう「People」や「Users」のように複数の名詞が所属するカテゴリー名になぞらえられます。

  package Person;

  use Moose;
  # now it's a Moose class!

アトリビュート

アトリビュートは、クラスを定義するプロパティです。アトリビュートには「かならず」名前があります。また、ほかのさまざまなプロパティを「持つこともあります」。

そのようなプロパティとしては、読み書きのフラグや、、アクセサメソッドの名前、委譲、デフォルト値などがあります。

アトリビュートはメソッド「ではありません」が、アトリビュートを定義するとさまざまなアクセサメソッドが生成されます。ふつうのアトリビュートであれば最低でもかならず読み取り用のアクセサメソッドがありますし、多くのアトリビュートには書き込み用のメソッドやクリア用のメソッド、(「値がセットされていますか」という)断定用のメソッドなどもあるものです。

アトリビュートには委譲が定義されていることもあります。これは委譲のマッピングに基づいて追加のメソッドを生成するものです。

デフォルトでは、Mooseはアトリビュートをオブジェクトのインスタンス(これはハッシュリファレンスです)の中に保存しますが、「これはMooseベースのクラスの作者には見えないことになっています」! Mooseのアトリビュートは「不透明な」オブジェクトのインスタンスの「プロパティ」であると考えておくのがいちばんです。これらのプロパティには、明確に定義されたアクセサメソッドを通じてアクセスできます。

アトリビュートはそのクラスに所属しているメンバなら持っている、というものです(たとえば、Peopleクラスのメンバなら姓と名を持っていますし、Usersクラスのメンバであればパスワードと最終ログイン日時を持っています)。

  has 'first_name' => (
      is  => 'rw',
      isa => 'Str',
  );

メソッド

メソッドは非常に単純。クラスの中で定義したサブルーチンはすべてメソッドです。

メソッドは動詞に対応するもので、オブジェクトができることをあらわします(たとえば、Userならログインできます)。

  sub login { ... }

ロール

ロールは、クラスが「果たす」役割をあらわすものです。また、クラスがロールを「消費する」という言い方もします。たとえば、MachineクラスならBreakableという役割を果たすかもしれません。Boneクラスもそうかもしれません。ロールは複数の無関係なクラスが共通して持つ(「壊せる」とか「色がついている」という)概念を定義するのに使います。

ロールには0個以上のアトリビュートがあります。

ロールには0個以上のメソッドがあります。

ロールには0個以上のメソッドモディファイアがあります。

ロールには0個以上の必須メソッドがあります。

必須メソッドはそのロールが実装するものではありません。必須メソッドは「このロールを使うにはかならずこのメソッドを実装する必要がある」という意味です。

ロールには0個以上の排除ロールがあります。

排除ロールというのは、その排除しようとしているロールとは合成できないロールのことです。

ロールはクラス(や、ほかのロール)に合成されるものです。ロールをクラスに合成すると、そのアトリビュートやメソッドはそのクラスに「埋め込まれます(フラット化)」。ロールが継承階層にあらわれることは「ありません」。ロールを合成すると、そのアトリビュートやメソッドは「その取り込んだクラスの中で」定義されていたかのように見えます。

ロールは、ほかのオブジェクト指向言語でいうミックスインやインタフェースに似たものです。

  package Breakable;

  use Moose::Role;

  has is_broken => (
      is  => 'rw',
      isa => 'Bool',
  );

  requires 'break';

  before 'break' => {
      my $self = shift;

      $self->is_broken(1);
  };

メソッドモディファイア

メソッドモディファイアは指定したメソッドが呼ばれたときに呼ばれる割り込みのことです。たとえば、「login()を呼ぶ前にこのモディファイアを先に呼んでほしい」のように言えるわけです。モディファイアには「before」、「after」、「around」、「augment」のようにさまざまな種類のものがあります。また、ひとつのメソッドに複数のモディファイアを適用することもできます。

メソッドモディファイアはよく親クラスのメソッドをオーバーライドするかわりに使われます。また、ロールの中でも、ロールを取り込む側のクラスのメソッドを修正する手段として利用されます。

メソッドモディファイアは、裏では単に指定したメソッドが呼ばれる前やあとに(あるいはそれを取り囲むように)呼ばれる昔ながらのPerlのサブルーチンにすぎません。

  before 'login' => sub {
      my $self = shift;
      my $pw   = shift;

      warn "Called login() with $pw\n";
  };

Mooseには(ミニチュアの)型システムもあります。この型システムを使うと、アトリビュートに型を定義できるようになります。Mooseには、StrNumBoolHashRefのように、Perlに用意されている型に基づいた組み込みの型がひと揃い用意されています。

また、アプリケーションで使われているクラスの名前はすべて、型の名前として使うことができます。

最後に、サブタイプとして、あるいは完全に新しい型として、独自の制約を持つ自前の型を定義することもできます。たとえば、Intのサブタイプとして、正数しか許さないPosIntという型を定義できます。

委譲

Mooseのアトリビュートには委譲を宣言的に定義する構文が用意されています。委譲というのは、実際の仕事はアトリビュートのメソッドを呼び出して行わせるメソッドのことです。

コンストラクタ

コンストラクタはオブジェクトのインスタンスを生成するものです。従来のPerlではnew()というメソッドを定義して、そこでリファレンスをblessするのがふつうでした。

Mooseはこのnew()というメソッドを生成して適切な処理をしてくれます。自分でコンストラクタを定義する必要はまったくありません!

場合によってはオブジェクトを生成するときに何かしたいことがあります。そのようなときはクラスにBUILD()メソッドを用意すると、Mooseが新しいオブジェクトを生成したあとでそのメソッドを呼んでくれます。

デストラクタ

これはオブジェクトのインスタンスがスコープの外に出たときに呼ばれる特殊なメソッドです。必要があれば、このメソッドの中でクラスに特殊な処理をさせられます(ただし、ふつうはそのようなことをする必要はありません)。

これは、従来のPerl 5ではDESTROY()メソッドですが、MooseではDEMOLISH()メソッドになります。

オブジェクトのインスタンス

オブジェクトのインスタンスは、たとえば具体的なひとりのPersonやUserのように、そのクラスの「カテゴリー」に属する具体的な名詞にあたります。インスタンスはクラスのコンストラクタによって生成されます。

インスタンスはアトリビュートの値を持っています(たとえば、具体的な人物には(具体的な)姓と名があります)。

インスタンスは、従来のPerl 5ではblessされたハッシュリファレンスであることが多いですが、Mooseの場合、オブジェクトのインスタンスが実際にどうなっているかを知る必要はまったくありません(はいはい。Mooseの場合でもふつうはblessされたハッシュリファレンスです)。

Mooseと従来のPerlの違いのまとめ

  • クラス

    シンボルテーブルの中を漁る以外にはイントロスペクションがないパッケージ。

    Mooseを使うと、明確に定義された宣言とイントロスペクションを利用できます。

  • アトリビュート

    手書きのアクセサメソッド。シンボルテーブルのハック。あるいはClass::Accessorのようなヘルパーモジュール。

    Mooseでは宣言的に定義され、メソッドとは明確に区別されています。

  • メソッド

    これはMooseでも従来のPerlとだいたい同じです。

  • ロール

    Class::TraitClass::Role、あるいはmixin.pmかも。

    Mooseではコア機能に含まれています。ほかのすべてのものと同様にイントロスペクション可能です。

  • メソッドモディファイア

    昔はシンボルテーブルを本気でいじらないとできませんでした。(少なくともPerl 5では)おそらくこれまでに見たことのある人はいなかったはずです。

  • new()メソッドやアクセサの中で手書きのパラメータチェック。

    Mooseでは宣言的に型を定義しておいて、アトリビュートの中でその型を名指しで利用するようになっています。

  • 委譲

    Class::Delegation or Class::Delegator, but probably even more hand-written code.

    Mooseではこれも宣言的にできます。

  • コンストラクタ

    new()メソッド。その中でリファレンスをblessします。

    Mooseでクラスを定義すればただでついてきます。

  • デストラクタ

    DESTROY()メソッド。

    MooseではDEMOLISH()と呼ばれます。

  • オブジェクトのインスタンス

    blessされたリファレンス(ふつうはハッシュリファレンスです)。

    Mooseでは中身は見えませんが、クラスによって定義されたアトリビュートやメソッドがたくさんあります。

  • 不変化

    Mooseには「不変化」と呼ばれる機能があります。クラスを不変化すると、メソッドやアトリビュート、ロールなどの追加は済んだとみなされ、Mooseがその場限りの非常に汚いコードを生成するテクニックを駆使してクラスを最適化し、オブジェクトの生成などを高速化します。

メタって何なの

メタクラスはクラスを説明するクラスです。Mooseを使って定義したクラスにはすべてmeta()メソッドが用意されるのですが、このメソッドが返すMoose::Meta::ClassオブジェクトのイントロスペクションAPIを使うと、そのクラスについての情報を調べることができます。

  my $meta = User->meta();

  for my $attribute ( $meta->get_all_attributes ) {
      print $attribute->name(), "\n";

      if ( $attribute->has_type_constraint ) {
          print "  type: ", $attribute->type_constraint->name, "\n";
      }
  }

  for my $method ( $meta->get_all_methods ) {
      print $method->name, "\n";
  }

メタクラスは、先ほど定義した概念のほとんどすべてにあります(だから、Moose::Meta::ClassMoose::Meta::AttributeMoose::Meta::MethodMoose::Meta::RoleMoose::Meta::TypeConstraintMoose::Meta::Instanceなどがあるわけです)。

どうしても自分のやり方でしたいときは

Mooseのすごいところのひとつは、深入りしていって何か「間違ったやり方」をしているものを見つけたとしても、メタクラスを拡張すれば変えられることです。たとえば、配列リファレンスをベースにしたオブジェクトを持てるようにもできますし、コンストラクタを厳密に(知らないパラメータは許さないように!)することもできます。アトリビュートのアクセサの命名規則を定義することもできますし、クラスをシングルトンにすることも、それ以上のことだってできるのです。

こういった拡張モジュールの多くは、驚くほど少ないコードしか要求しませんし、一度作ってしまえば、「自分ならこうする」というコードをまた手書きする必要はなくなります(そのかわりに自分の好きな拡張モジュールをロードしてしまえばよいのです)。

  package MyWay::User;

  use Moose;
  use MooseX::StrictConstructor
  use MooseX::MyWay;

  has ...;

次にするべきことは

Mooseの売り文句に納得していただけたら、今度は本当に使い方を学ぶ番です。

Mooseをそのまま従来のPerl 5のオブジェクト指向のコードで書き直したらどうなるかを見たい方は、Moose::Manual::Unsweetenedをご覧ください。「Mooseの流儀」の一端を手っ取り早く理解するには便利かもしれません。

あるいは、それは飛ばして、まっすぐMoose::Manual::Classesや、残りのMoose::Manualに行ってもよいでしょう。

そのあとは、Moose::Cookbookから始めることをおすすめします。基本のセクションのレシピをすべて読み終えたら、Mooseの動作や、基本的なオブジェクト指向の機能についてはすべて、かなりよく理解できているはずです。

そのあとは、ロールのレシピをご覧ください。本気で知りたいのであれば、そのままメタや拡張モジュールについてのレシピも読んでいきましょう。ただし、これらはおもにMooseの達人になりたい人や、Mooseの挙動を変えたい人向けのものです。

作者

Dave Rolsky <[email protected]>

コピーライト & ライセンス

Copyright 2008-2009 by Infinity Interactive, Inc.

http://www.iinteractive.com

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.