Perl Hackers Hub

第13回メタオブジェクトプロトコル入門(1)

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回はPerl 5上で先進的なオブジェクト指向を実現するためのフレームワークMooseなどの開発に関わっているShawn Moore(sartak)さんと、Japan Perl Associationの理事である牧大輔さんが、⁠メタオブジェクトプロトコル」について解説します。

メタオブジェクトプロトコル(MOP)とは

さまざまなプログラミング言語がオブジェクト指向機能を提供していますが、オブジェクトの構造を細部にわたって閲覧したり、デフォルトの挙動を拡張したりすることまでできる言語はそれほど多くありません。

このような操作を行いたい場合は「メタオブジェクトプロトコル」⁠以降:MOP)と呼ばれる技術を利用します。MOPは、オブジェクトのメソッドやアトリビュートなどを含む内部構造そのものを、オブジェクトで表現するしくみです。Perlだけでなく、いろいろなプログラミング言語で実装されています。

MOPでは、内部に隠蔽(いんぺい)されたメタ情報だけではなく、クラス・メソッド・ロールなどを汎用的なオブジェクト群で表現することによって、オブジェクト定義のあらゆる部分を操作することが可能になります。しかもオブジェクトを利用しているので、該当クラスを継承することで容易に挙動を変更できます。またこれ以外にも、動的(機械的)にオブジェクトを作成することが簡単になったりと、さまざまな便利な利用法があります。

MOPを利用することで、オブジェクト指向の原理を犠牲にせずに、クラス実装の柔軟性を上げるという恩恵にあずかれます。MOPによって得られる柔軟性は、オブジェクト指向という概念の設計者たちが思いつかなかったような機能の追加も可能にしてくれます。

MOPの基本

それではMOPの挙動を見ていきましょう。まずMOPを使ってクラスを定義してみます。本稿では便宜上、すべてMOPベースのクラスビルダーであるClass::MOPおよびMooseを使用しますが、これらと互換のAPIを実装しているツールなら同等のことができます。

Class::MOPのインストール

Class::MOPは後述のMooseに同梱されているモジュールです。Mooseをインストールするにはcpanmcpanコマンドを使用してCPANからインストールします。

> cpanm Moose

cpanmがインストールされていない場合は、以下でもインストールできます。

> curl -Lk http://xrl.us/cpanm | perl - Moose

クラスの定義

Class::MOPを使ってクラスを定義するにはClass::MOP::Classのオブジェクトインスタンスを生成します。

このオブジェクトは「メタクラス」と言われ、任意のクラスを表現します。このメタクラスに対して操作を行うとメソッドやアトリビュートの定義もできますリスト1⁠。

リスト1 MOPによるクラスの定義
use strict;
use Class::MOP;

# (1)クラスの作成
my $meta = Class::MOP::Class->create('Person');

# (2)アトリビュートの追加
$meta->add_attribute(
    'name',
    accessor => 'name',
);

# (3)メソッドの追加
$meta->add_method( describe => sub {
    my $self = shift;
    my $name = $self->name;
    print "$name";
} );

# (4)オブジェクトインスタンスの生成
my $object = $meta->new_object( name => "Shawn" );
$object->describe();

まずリスト1(1)でPersonというクラス用のメタクラスインスタンスを生成します。createには作成するクラス名を引数として渡します。

リスト1(2)nameというアトリビュートを定義します。accessor => 'name'でこのアトリビュートにアクセスするためのアクセサ名を指定しており、指定することにより読み書き両方を行えるアクセサが作成されます。読み出しのみ、書き込みのみを行う場合はreaderwriterなどを指定します。

リスト1(3)でクラスにメソッドを追加します。ここでは単純にオブジェクトインスタンスの情報を出力するメソッドdescribe()を定義しました。

ここまでで一通りクラス定義ができたので、オブジェクトインスタンスを生成します。これには通常のnew()コンストラクタではなく、MOPのインスタンス生成の窓口であるnew_object()を使用します。リスト1(4)ではnew_object()を使用し、必要な引数を渡してオブジェクトを生成しています。

クラスの継承

では、クラスを継承して拡張する場合はどのようになるのでしょうか。

リスト2に、Personの子クラスとしてEmployee(従業員)を作成します。

リスト2 MOPによるサブクラスの定義
use strict;
use Class::MOP;
use Person;

# (1)クラスの作成
my $meta = Class::MOP::Class->create('Employee');

# (2)親クラスの指定
$meta->superclasses('Person');

$meta->add_attribute(
    'employer',
    accessor => 'employer',
);

# (3)メソッドのオーバーライド
$meta->add_method(
    'describe',
    sub {
        my $self = shift;
        my $company = $self->employer->name;
        my $name = $self->name;
        print "所属 $company, 社員名 $name";

        # あとは親クラスにおまかせ
        $self->next::method();
    }
);

まずリスト2(1)でPersonのときと同じくメタクラスインスタンスを生成します。

親クラスの指定はsuperclassesというメソッドに親クラス名を指定することで実装できます。リスト2(2)ではPersonを親クラスとして継承関係を宣言します。このメソッド呼び出し内で従来のPerlで必要だった@ISAの管理を行ってくれます。

リスト2(3)ではadd_method()をEmployeeクラスでも呼ぶことにより、Personで定義されたメソッドのオーバーライドを行っています。親クラスのメソッドを呼ぶ際にClass::MOP/Mooseはmro.pmを使用しているので、SUPER::記法ではなくnext::methodを使っています。

メソッドモディファイア

Class::MOPでは前項のメソッドオーバーライドのほかにも「メソッドモディファイア」というしくみが使えるので、通常のメソッドの前後などにメソッドをフックすることができます。

具体的にはリスト3のように、describe()にメソッドモディファイヤ(メソッド実行時のフック)を登録して、親クラスのメソッド実行前にリスト2(3)と同様の情報を出力するように指定できます。

リスト3 メソッドモディファイア
$meta->add_before_method_modifier(
    'describe',
    sub {
        my $self = shift;
        my $company = $self->employer->name;
        print "所属 $company, 社員名 ";
    },
);

クラスの調査

メタクラスを利用することにより、任意のクラスのさまざまな情報を取得(イントロスペクション)できます。実際に先ほど作成したEmployeeクラスの情報を調べてみましょうリスト4⁠。

リスト4 MOPでクラスを調べる
use strict;
use Employee;

# (
)メタクラスの取得
my $class = Employee->meta;

# "Employee"
print $class->name, "\n";
# "Person"
print $class->superclasses, "\n";

# (2)メタアトリビュート
my $attribute =
    $class->find_attribute_by_name('employer');
my $accessor = $attribute->accessor;

# (3)メタメソッド
my $method = $class->find_method_by_name( $accessor );

# "employer"
print $method->name, "\n";
# "Employee::employer"
print $method->fully_qualified_name, "\n";

まずリスト4(1)で、Employeeのメタクラスを得るためにEmployee->metaを呼び出します。MOPを使用して作成されたクラスには自動的にこのクラスメソッドが定義され、メタクラスを取得できるようになっています。

リスト4(2)find_attribute_by_nameを使用してアトリビュートを表現するオブジェクト「メタアトリビュート」を取得します。メタアトリビュートは該当アトリビュートの情報を格納するほか、型制約の確認やアトリビュートのデフォルト値を生成するためのロジックの管理なども行います。

リスト4(3)ではアトリビュートのアクセサを表す「メタメソッド」を取得しています(厳密にはこれはメタメソッドであるClass::MOP::Methodではなくて、アクセサを表現するためのサブクラスのClass::MOP::Method::Accessorです⁠⁠。このオブジェクトはどのアトリビュートのアクセサであるかなどの情報を格納しているほか、書き込み・読み込みアクセサ用のメソッドのコードを生成できます。

このようなメタクラス・メタアトリビュートやメタメソッドなどを「メタレイヤ⁠⁠、通常のクラスを「基本レイヤ」と呼びます。MOPの最大の特徴の一つは、通常レイヤのクラス定義に関連するさまざまな情報が、メタレイヤで取得・確認できるようになることです。

おすすめ記事

記事・ニュース一覧