The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Object::Simple::Guide::Ja - Object::Simpleのガイドブック

ガイドブック

概要

オブジェクト指向プログラミング のひとつのよい習慣は、メソッドのみによってオブジェクト が持っているデータにアクセスすることです。 オブジェクトが持っているデータを属性値と呼びます。 属性値を取得・設定するためのメソッドをアクセッサと呼びます。

アクセッサの定義は通常は次のようになります。

    package Point;
    
    sub x {
        my $self = shift;
        
        if (@_) {
            $self->{x} = $_[0];
        }
        return $self->{x};
    }

オブジェクトはたくさんの属性値を持つことが多いですから、 アクセッサを簡単な方法で作成したいと思うことでしょう。

Object::Simpleはアクセッサを生成するためのメソッドを提供します。 次の記述でアクセッサを生成することができます。

    __PACKAGE__->attr('x');

Perlのモジュールの中にはアクセッサを生成するための モジュールが他にも、Object::Simpleの特徴は 次のとおりです。

Object::Simpleを利用すると、属性値にデフォルト値 を持たせることができます。これは Class::Accessor::Fastに対する利点です。

    __PACAKGE__->attr(x => 0);

Object::Simpleはコンストラクタであるnew()を提供します。 コンストラクタはハッシュとハッシュのリファレンス を受け取ることができます。

    my $point = Point->new(x => 1, y => 1);
    my $point = Point->new({x => 1, y => 1});

これもClass::Accessor::Fastに対する利点です。 Class::Accessor::Fastnew()はハッシュリファレンス しか受け取ることができないからです。

Object::Simpleを利用すると、きわめて短い記述でアクセッサ を定義することができます。 attr()を使って、すべてのアクセッサを定義することもできます。

    __PACKAGE__->attr(
        [qw/foo bar baz/],
        some => 1,
        other => sub { 5 }
    );

これはMooseなどのクラスビルダーに対する利点です。

Object::Simpleには機能が足りないのではないかと 感じる方もいるかもしれませんが、 デフォルト値を定義する機能さえあればほとんどの場合に 十分です。 たとえばMojoliciousという大きななWebフレームワーク のアクセッサでは デフォルト値を定義する機能だけを用いています。

Object::Simpelはプログラマが使いやすいように設計されています。 Object::Simpelを使ってみたいと思われた方は 以下の解説を読んでみてください。

1. アクセッサの生成

最初にObject::Simpleを継承したクラスを作成します

    package MyClass;
    
    use base 'Object::Simple';

継承はbaseモジュールを使って行うのが一般的です。

Object::Simplenew()を持っています。 new()はコンストラクタであり ハッシュあるいはハッシュのリファレンスを受け取ることができます。

    my $obj = MyClass->new;
    my $obj = MyClass->new(foo => 1, bar => 2);
    my $obj = MyClass->new({foo => 1, bar => 2});

アクセッサを生成するにはattr()メソッドを使用します。

    __PACKAGE__->attr('foo');

生成されたアクセッサを使って属性値を設定したり取得することができます。

    # 属性値の設定
    $obj->foo(1);
    
    # 属性値の取得
    my $foo = $obj->foo;

attr()では、アクセッサのためのデフォルト値を指定することもできます。

    __PACKAGE__->attr(foo => 1);

属性値が設定されていない場合は最初にアクセッサが呼び出されたときに デフォルト値が属性値に設定されます。

    my $default_value = $obj->foo;

もしリファレンスやオブジェクトをデフォルト値として指定したい場合は サブルーチンのリファレンスの戻り値にする必要があります。 これはデフォルト値を他のオブジェクトと共有しないためです。

    __PACKAGE__->attr(foo => sub { [] });
    __PACKAGE__->attr(foo => sub { {} });
    __PACKAGE__->attr(foo => sub { MyClass->new });

複数のアクセッサを一度に生成することができます。

    __PACKAGE__->attr([qw/foo bar baz/]);
    __PACKAGE__->attr([qw/foo bar baz/] => 0);

すべてのアクセッサを一度に生成することもできます。

    __PACKAGE__->attr(
        [qw/foo bar baz/],
        some => 1,
        other => sub { 5 }
    );

引数が奇数個の場合には、一つ目の引数に渡された値は、 デフォルト値を持たないアクセッサです。 それ以降は、デフォルト値を持つアクセッサの定義になります。

クラスの作成

Object::Simpleを継承してクラスを作成してみましょう。 PointクラスとPoint3Dクラスを作成してみます。

Pointクラス:

Pointは点を表すクラスです。 xyというアクセッサと、 xyの値を0にクリアする<clear>というメソッドを持ちます。

    package Point;
    
    use strict;
    use warnings;
    
    use base 'Object::Simple';

    __PACKAGE__->attr(x => 0);
    __PACKAGE__->attr(y => 0);
    
    sub clear {
        my $self = shift;
        
        $self->x(0);
        $self->y(0);
    }

Pointクラスは以下のように使用することができます。

    use Point;
    my $point = Point->new(x => 3, y => 5);
    print $point->x;
    $point->y(9);
    $point->clear;

Point3Dクラス:

Point3Dは3次元の点を表すクラスです。 x,y,zというアクセッサと、 x,y,zの値を0にクリアする<clear>というメソッドを持ちます。

Point3DPointを継承して作成されています。 clear()メソッドはxyzの値をクリアするために オーバーライドされています。

    package Point3D;
    
    use strict;
    use warnings;
    
    use base 'Point';
    
    __PACKAGE__->attr(z => 0);
    
    sub clear {
        my $self = shift;
        
        $self->SUPER::clear();
        
        $self->z(0);
    }

Point3Dクラスは以下のように利用することができます。

    use Point3D;
    my $point = Point->new(x => 3, y => 5, z => 8);
    print $point->z;
    $point->z(9);
    $point->clear;

2. オブジェクト指向プログラミングの概念

継承

Object::Simpleをよく理解するために、 オブジェクト指向の概念を解説したいと思います。

オブジェクト指向の一つ目の概念は「継承」です。 「継承」とは「クラスQがクラスPを継承していたら、 クラスQはクラスPのすべてのメソッドを呼び出すことができる」 ということを意味します。

    +---+
    | P | Base class
    +---+   having method1() and method2()
      |
    +---+
    | Q | Sub class
    +---+   having method3()

クラスQはクラスPを継承しているので、 クラスQはクラスQのメソッドに加えて、クラスPのすべてのメソッド を呼び出すことができます。 言い換えれば、クラスQは method1(), method2()method3() を呼び出すことができます。

継承を行うには、baseモジュールを使用します。

    package P;
    
    sub method1 { ... }
    sub method2 { ... }
    
    package Q;
    
    use base 'P';
    
    sub method3 { ... }

Perlはオブジェクト指向プログラミングを助ける 便利な関数とメソッドを持っています。

オブジェクトがどのクラスに属しているかを知るには、 ref()関数を使用します。

    my $class = ref $obj;

オブジェクトが特定のクラスを継承しているかどうかを調べるには、 isa()メソッドを使用します。

    $obj->isa('MyClass');

オブジェクト(あるいはクラス)が特定のメソッドを呼び出す ことができるかどうかを知るには、 can()メソッドを使用します。

    MyClass->can('method1');
    $obj->can('method1');

カプセル化

オブジェクト指向プログラミングのふたつ目の概念は カプセル化です。 「カプセル化」は「内部的にデータに直接アクセスしてはいけない」 ということを意味します。 ドキュメントに記述された公開されたメソッドを使用しなければ なりません。 このルールを守ることによって、すべてのことがシンプルになります。

このルールを守るためには 値を取得や設定を行うためのアクセッサを生成する 必要があります。

    my $value = $obj->foo;
    $obj->foo(1);

直接データにアクセスするのは良くない習慣です。

    my $value = $obj->{foo}; # Bad manner!
    $obj->{foo} = 1;         # Bad manner!

ポリモーフィズム

オブジェクト指向プログラミングの三つ目の概念は 「ポリモーフィズム」です。 「ポリモーフィズム」は、 「オーバーロード」と「オーバーライド」のふたつの概念に 分割されます。

Perlプログラマはオーバーロードを気にする必要はありません。 Perlは動的な言語なので、 サブルーチンはどのような値でも受け取ることができます。 オーバーロードはC++やJavaなどの 静的な型を持つ言語にとって価値があります。

「オーバーライド」は「サブクラスにおいて、基底クラスのメソッドを 変更することができる」ということを意味します。

    package P;
    
    sub method1 { return 1 }
    
    package Q;
    
    use base 'P';
    
    sub method1 { return 2 }

クラスPのmethod1()は1という値を返却します。 クラスQのmethod1()は2という値を返却します。 つまり、クラスQにおいて、method1()はオーバーライド されたということです。

    my $obj_a = P->new;
    $obj_p->method1; # Return value is 1
    
    my $obj_b = Q->new;
    $obj_q->method1; # Return value is 2

もし基底クラスのメソッドをサブクラスから呼び出したい場合は SUPER擬似クラスを使用します。

    package Q;
    
    sub method1 {
        my $self = shift;
        
        my $value = $self->SUPER::method1(); # return value is 1
        
        return 2 + $value;
    }

これらの三つの概念だけを理解するならば、 十分強力なオブジェクト指向プログラムができ、 ソースコードは他の言語のユーザから見ても 読みやすいものになるでしょう。

3. よく利用するテクニック

new()のオーバーライド

new()はオーバーライドすることができます。

オブジェクトの初期化

オブジェクトの初期化処理を行いたい場合は、 new()をオーバーライドすることができます。

    sub new {
        my $self = shift->SUPER::new(@_);
        
        # 初期化処理
        
        return $self;
    }

new()の引数の変更

new()の引数を変更したい場合は、 new()をオーバーライドすることができます。

    sub new {
        my $self = shift;
        
        $self->SUPER::new(x => $_[0], y => $_[1]);
        
        return $self;
    }

new()メソッドをオーバーライドすることによって、 new()に配列を渡すことができるようになりました。

    my $point = Point->new(4, 5);

4. その他の特徴

引数のチェック

間違った個数の引数がnew()に渡された場合は 例外が発生します。

    my $obj = MyClass->new(1); # 例外発生!

アクセッサにおいては2つ以上の数の引数が渡された場合に 例外が発生します。

    $obj->foo(a => 1); # 例外発生

メソッドのインポート

Object::Simpleのメソッドをインポートすることもできます。

    package MyClass;
    
    use Object::Simple qw/new attr/;
    
    __PACKAGE__->attr('foo');

new()はクラスにインポートされるのであって、 基底クラスから継承したわけではないので、 new()メソッドをオーバーライドすることはできない ということに注意してください。

メソッドチェーン

アクセッサは、値を設定するために呼ばれたときに、 自分自分のオブジェクトを返却するので、メソッドチェーンを 行うことができます。

    $obj->foo(1)->bar(4)->baz(6);

オブジェクトからのattr()の呼び出し

オブジェクトからattr()を呼び出すこともできます。

    $obj->attr(foo => 1);

対象のオブジェクトが属するクラスにアクセッサが追加されます。

安定性

(2011/2/23)

Object::Simpleはとても安定しています。 Object::Simpleは後方互換性を維持することに努めます。

ドキュメントに記述されているメソッドについては 名前を変更したり、取り除いたりすることはないでしょう。

class_attrdual_attrといった廃止予定のメソッド は決して利用しないでください。遠い将来十分な期間をおいて、 取り除かれるかもしれません。