OOP(3) 継承

パッケージ変数である @ISA 配列に、親となるクラス(パッケージ)の名前を入れておけば、そのクラスを継承した事になります。(is-a の関係)
メソッドが呼び出された時、そのメソッドが見つからなければ @ISA 配列の中の親クラスが持っていないか探してくれます。親クラスに見つからなければ、さらに親の親クラス……と再帰的にたどっていくので、継承のツリーができますね。
ちなみに、@ISA は配列なので複数のクラスを指定できます。すると、多重継承した事になります……が、僕はあまり多重継承が好きではないので、「できる」という事だけ覚えておきますね。
では、早速クラス継承のコードを書いてみます。

package Bird;
 
sub new {
	my $class = shift;
	return bless {}, $class;
}
 
sub sing {
	print "Cheep cheep...\n";
}
 
package Cock;
our @ISA = ("Bird");
 
sub sing {
	print "Cock-a-doodle-doo!\n";
}
 
package main;
 
$abird = Bird->new();
$abird->sing();
 
$acock = Cock->new();
$acock->sing();

以下が実行結果です。

Cheep cheep...
Cock-a-doodle-doo!

@Cock::ISA の中に "Bird" を加えているので、Cock クラスは Bird クラスを継承しています(Cock is-a Bird)。
sing メソッドで鳴きますが、Cock 側で sing メソッドをオーバーライド(メソッドの上書き)しているので、鳴き声が変わっているのがわかります。
ポイントはコンストラクタ(new)です。クラスメソッドの場合、第一引数にはクラス名が渡されるので、そのクラス名を使って bless しています。
実は、最初は bless {}; と、クラス名を書かずに bless していました。

sub new {
	return bless {};
}

すると

Cheep cheep...
Cheep cheep...

と $acook も Bird のオブジェクトになってしまうのです。(-_-;)
メソッド呼び出しの動きをよく考えると、原因がわかりました。
コンストラクタは Bird クラスにしか書いていないので、Cock->new() を呼び出すと、@ISA をたどって Bird クラスの new メソッド(Bird::new)が呼ばれる事になります。
そして Bird::new の中で bless {}; したリファレンスを返しているわけですが、bless する時にパッケージ名を省略したのが間違いでした。
bless のパッケージ名を省略すると、カレントパッケージが使われる事は昨日勉強しました。Bird::new の中ではカレントパッケージは Bird ですので、bless {}; と呼び出すと Bird パッケージが bless されてしまう事になります。
すると、Bird->new() しても Cock->new() しても、Bird オブジェクトが返ってきてしまうのです。これでは、鳴き声が同じになってしまいますね。

Cock->new();          Bird->new();
    ↓(@ISAをたどる)      ↓
Bird::new("Cock");    Bird::new("Bird");
    ↓                    ↓
bless {};             bless {};
    ↓                    ↓
Bird オブジェクト     Bird オブジェクト

そこで、渡されたクラス名($class)を使って bless する事で、メソッドの呼び出しに使われたパッケージに従ったオブジェクトを生成する事ができるようになります。

Cock->new();          Bird->new();
    ↓(@ISAをたどる)      ↓
Bird::new("Cock");    Bird::new("Bird");
    ↓                    ↓
$class = "Cock";      $class = "Bird";
    ↓                    ↓
bless {}, $class;     bless {}, $class;
    ↓                    ↓
Cock オブジェクト     Bird オブジェクト

こんな感じでしょうか。
失敗しましたが、継承の機構が理解できたので逆に良かったです。
それにしても、ただのクラス名の配列が動的な継承ツリーになるのは面白いです。スクリプト言語Perl ならではですね。シンプルでわかりやすいです。