オブジェクト編その8-NEXT

このメソッドには続きがあるよ「NEXT.pm」

前回のやり方では初期化を下層クラスで行っていて、すこし処理に手間をかけています。
復習です。

  • 上位階層「鳥クラス」「獣クラス」を継承した「こうもりクラス」が存在します。
  • 「鳥クラス」は属性として羽を持ち、「獣クラス」は属性として牙と毛皮を持っています。
  • 属性の初期化をメソッドで行います。
  • 属性の初期化を行うメソッドは各クラスに同名で存在する。
  • 名前の衝突がおきないように「こうもりクラス」側で「鳥クラスのinit()」と「獣クラスのinit()」を実行する処理を実装している

これでも問題なく動作しますが、「鳥」「獣」のほかに「哺乳類」というクラスも継承したい場合はまた「こうもりクラス」に「哺乳類クラスのinit()」を実行する処理を追加する必要があります。

今一度思い起こす必要があります。やりたいのはオブジェクト風の記述ではなく、再利用です。

ということで工夫を試みます。

  • 親クラスが2つ以上(多重継承)の場合、最初の親のメソッドを実行します。ここまでは今までと同じです。
  • その後、次のクラスに同名のメソッドがあれば実行する。

次のスクリプトを記述して、実行してみてください。

C:\work\exaple\oop_008.pl

package main;
use strict;
use warnings;

my $bat = Bat->new( fur => 'soft', fang => 'small', wing => 'black' );

$bat->speak;

package Initialiazable;
sub new {
	my $class = shift;
	my $self = bless {}, $class;
	$self->init(@_);
	return $self;
}

package Beast;
use strict;
use warnings;
use base qw(Initialiazable);

sub init {
	my( $self, %param ) = @_;

	$self->{_fur}  = $param{fur};
	$self->{_fang} = $param{fang};

	return $self;
}

package Bird;
use strict;
use warnings;
use base qw(Initialiazable);

sub init {
	my( $self, %param ) = @_;

	$self->{_wing} = $param{wing};

	return $self;
}

package Bat;
use strict;
use warnings;
use vars qw( @ISA );
use base qw/Beast Bird/;

sub init {
	my ( $self, %param ) = @_;
	
#	$self->Beast::init(%param);
#	$self->Bird::init(%param);

	for my $class ( @ISA ) {
		my $init = $class->can('init');
		$self->$init(%param) if $init;
	}

	return $self;
}

sub speak { 
	my $self = shift;
	
	for my $key ( keys %$self ) {
		print "My $key is ",  $self->{$key}, "\n";
	}
}

実行してみましょう。


実行結果は同じです。これなら新しく哺乳類クラスを追加しても大丈夫です。

それじゃNEXTしてみましょう。

これまでのスクリプトで、やりたかった事をまとめます。

  • 各クラスの属性を初期化した。
  • つまり各クラスの属性を初期化するメソッドを実行したい。
  • そのメソッドは各クラスに個別に実装されている。
  • つまりそのメソッドを全て実行したい。
  • その処理を下層クラスが代表して実装、実行する。
  • 新しく継承するクラスが増えてきてもその実装は変更したくない
  • つまり@ISAに新しい名前空間が入ってきて、init()メソッドを持っていたらそれも実行したい。
  • なのでUNIVERSALクラスのcanメソッドで判定、実行する。

もし、次のことができれば上記と同じことができそうじゃないですか?

  • @ISAに格納されている最初のクラスのinit()メソッドを呼び出す。
  • その最初のクラスが@ISAに格納されている次のクラスにもしinit()メソッドがあれば呼び出す。
  • なければその次のクラスにinit()メソッドがあるかを調べる。

やってみましょう。さきほどのスクリプトを修正してください。

package main;
use strict;
use warnings;

my $bat = Bat->new( fur => 'soft', fang => 'small', wing => 'black' );

$bat->speak;

package Initialiazable;
sub new {
	my $class = shift;
	my $self = bless {}, $class;
	$self->init(@_);
	return $self;
}

package Beast;
use strict;
use warnings;
use base qw(Initialiazable);

sub init {
	my( $self, %param ) = @_;

	$self->{_fur}  = $param{fur};
	$self->{_fang} = $param{fang};

	return $self;
}

package Bird;
use strict;
use warnings;
use base qw(Initialiazable);

sub init {
	my( $self, %param ) = @_;

	$self->{_wing} = $param{wing};

	return $self;
}

package Bat;
use strict;
use warnings;
use vars qw( @ISA );
use base qw/Beast Bird/;

sub init {
	my ( $self, %param ) = @_;
	
#	$self->Beast::init(%param);
#	$self->Bird::init(%param);

	for my $class ( @ISA ) {
		my $init = $class->can('init');
		$self->$init(%param) if $init;
	}

	return $self;
}

sub speak { 
	my $self = shift;
	
	for my $key ( keys %$self ) {
		print "My $key is ",  $self->{$key}, "\n";
	}
}

どうやら実行結果は同じになるみたいです。
NEXT.pmを使うと、下層クラスでは何もしなくても属性を引き継ぐことができました。より再利用がしやすくなっていると思います。

ただし、NEXT.pmは構成が複雑になるとメソッドの解決順が意図しない順番になるという問題があります。クラスの設計者が意図しない動作をするとなると、再利用できません。

次回はダイヤモンド継承など、複雑な継承でもメソッドの呼び出し順を制御できるClass::C3を解説したいと思います。