2006 年 4 月 24 日 21 時 28 分

継承とコードリファレンス


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


property モジュールはコードリファレンスを使って、
該当パッケージのメソッドを関数のように呼び出す。
これは、継承の問題と関連し、以下の問題を生じる。

1. 継承したメソッドをプロパティ化できない

あるクラスを継承したクラスを作る場合、
祖先クラスに get_XXX/set_XXX のメソッドがあると、
継承したクラスはそれらを利用できる。

しかし、それらをオーバーライドしない限り、
メソッドの実体は祖先クラスにあり、
継承クラスにはメソッドの定義自体が存在しない。

つまり、継承クラスのシンボルテーブルには
get_XXX/set_XXX は存在しない。
そのため、継承したクラスが祖先クラスのメソッドを用いて
プロパティを定義するためには、
祖先クラスのメソッドを property に引き渡す必要がある。

    use property
            'name' => 'value',
            'set' => \&<実装パッケージ>::set_value,
            'get' => \&<実装パッケージ>::get_value;

しかし、通常は set_XXX/get_XXX の実際が、
どこで定義されてるかなどは分からない。
継承のどこかでオーバーライドされている可能性もある。

つまり、これは継承したクラスの作成者が、
メソッドがどこで定義されているか知っている必要がある。
オブジェクト指向的に、こういう設計は許されない。

2. プロパティをオーバーライドできない

祖先クラスが property モジュールを使って
プロパティを実装していた場合、
そのクラスには新しい lvalue メソッドが追加される。

このクラスを継承して新しいクラスを作った場合、
祖先クラスにある lvalue メソッドも継承され、
継承したクラスはそれらを利用することができる。

しかし、継承した lvalue メソッドは、
set_XXX/get_XXX を関数として呼び出すため、
set_XXX/get_XXX を継承クラスでオーバーライドしても、
lvalue メソッド経由では、祖先クラスのメソッドが呼ばれ、
オーバーライドしたメソッドが呼ばれないことになる。

1 番は明確だが、2 番は少し分かりにくいため、
実際にコードを書いて検証してみよう。

    package Base;

    use property
            'name' => 'value',
            'get' => \&get_value,
            'set' => \&set_value;

    sub new {
        my $package = shift;
        my $value = shift;
        bless({'value' => $value}, $package);
    }

    sub get_value {
        my $object = shift;
        $object->{'value'};
    }

    sub set_value {
        my $object = shift;
        my $value = shift;
        $object->{'value'} = $value;
    }

    package Derived;

    use Carp;

    our @ISA = ('Base');

    sub set_value {
        my $object = shift;
        my $value = shift;
        croak "Can't accept negative value." if $value < 0;
        SUPER->set_value($value);
    }

    package main;

    my $obj = Derived->new(3000);

    print "Value: ", $obj->get_value, "\n";

    $obj->value = -50;

    print "Value: ", $obj->value, "\n";

    $obj->set_value(-50);

    print "Value: ", $obj->value, "\n";

Base は、set_value と、get_value を定義し、
property モジュールを使って、value プロパティを作る。
これらは単純に値を設定したり返したりするだけだ。

Derived は Base を継承し、set_value をオーバーライド。
set_value は、設定値を確認し、負の値を拒否する。
そうでなければ、親クラスの set_value を呼び出す。

set_value/get_value は、メソッドとして呼び出すので、
Perl の継承のメカニズムによって処理される。
そのため、$obj->set_value は、Derived で処理され、
$obj->get_value は、Base で処理されてうまくいく。

しかし、$obj->value = -50 としたときは問題が起きる。
「value」の呼び出しは、継承が考慮されるので、
Base の value メソッドが呼び出される。

value メソッドへの代入は、マジカルスカラの機能により、
「Base::set_value」への呼び出しに転送される。
コードリファレンス経由であるため、
「Derived::set_value」は呼び出されないのだ。

そのため、$obj->value = -50; の呼び出しは、
croak せずに成功してしまう。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.