オブジェクト編その7-属性の初期化

初期化あとまわしでいいんじゃない?

前回までの復習です。

  • Perlのオブジェクトは元々は変数のリファレンスです。
  • blessするとオブジェクトとして振る舞います。
  • その性質はblessで関連づけられたクラスの性質を引き継ぎます。
  • そのクラスは他のクラスの性質を引き継ぐことができます。
  • どのクラスの性質を引き継いでいるかは@ISAという配列で管理しています。
  • blessで関連付けられたクラスにメソッドがない場合は@ISAにあるクラスにそのメソッドがないか調べます。-@ISAに複数のクラスが指定されている場合、最初に発見されたメソッドを使います。

メソッドはこれでかまわないのですが、今回は属性の継承に起こる問題を解説したいと思います。
といっても注意点は次のことだけです。

コンストラクタ(newメソッドの呼び出し)は何回やってもいいんだけど、属性の定義はコンストラクタがすべて終了してから。

コンストラクタは大体newメソッドのことです(DB関連ではconnectというメソッドにしている場合も)。
では多重継承で属性の指定が何度も行われていた場合に、クラス設計者が意図していない動作をする場面を見てみましょう。

獣、鳥がもつ属性

  • 獣は毛皮と牙を持っている
  • 鳥は羽をもっている

獣、鳥を多重継承した「柔かい毛皮で、小さな牙、黒い羽があるこうもり」のオブジェクトを生成します。

次のスクリプトを記述して、実行してみてください。
C:\work\exaple\oop_007.pl

package main;
use strict;
use warnings;
my $bat = Bat->new( fur => 'soft', fang => 'small', wing => 'black' );

$bat->speak;

package Beast;
use strict;
use warnings;
use vars qw(@ISA);

sub new { 
	my ( $class, %param ) = @_;
	bless {
		_fur  => $param{fur},
		_fang => $param{fang},
	}, $class;
}

package Bird;
use strict;
use warnings;

sub new { 
	my ( $class, %param ) = @_;
	bless {
		_wing  => $param{wing},
	}, $class;
}

package Bat;
use strict;
use warnings;
use base qw/Beast Bird/;
	
sub speak { 
	my $self = shift;
	
	for my $key ( keys %$self ) {
		print "My $key is ",  $self->{$key}, "\n";
	}
}

黒い羽を持たないこうもりができてしまいました。獣にも、鳥にもそれぞれnewメソッドが定義されていますが、このスクリプトは獣のnewメソッドを実行していません。

改良しましょう。

#!C:\Perl\bin\perl
package main;
use strict;
use warnings;

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

$bat->speak;

package Beast;
use strict;
use warnings;

sub new { 
	my ( $class, %param ) = @_;
	my $self = bless {}, $class;
	$self->init( %param );
}

sub init {
	my($self, %param ) = @_;
	$self->{_fur}  = $param{fur};
	$self->{_fang} = $param{fang};
}

package Bird;
use strict;
use warnings;

sub new {
	my ( $class, %param ) = @_;
	my $self = bless {}, $class;
	$self->init( %param );
}

sub init {
	my($self, %param ) = @_;
	$self->{_wing}  = $param{wing};
}

package Bat;
use strict;
use warnings;
use base qw/Beast Bird/;

sub new {
	my ( $class, %param ) = @_;
	my $self = bless {}, $class;

	$self->Beast::init( fur => $param{fur}, fang => $param{fang} );
	$self->Bird::init( wing => $param{wing} );
	
	return $self;
}

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

どうやら多重継承におけるデータの初期化問題を解決できたようです。
コンストラクタ時(newメソッド実行時)に空の変数をblessしておき、後からその変数に属性設定を行っています。

つまり、属性を継承しないで、属性を設定するメソッドを定義することによって問題を解決しました。
もう少しソースコードをすっきりさせてみましょう。

この動物関連のクラスではすべてnewコンストラクタを定義しています。この動物クラスの上位階層にInitialiazableクラスを作成し、獣、鳥クラスに継承させましょう。

package main;
use strict;
use warnings;

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

$bat->speak;

package Initialiazable;
use strict;
use warnings;

sub new { 
	my ( $class, %param ) = @_;
	my $self = bless {}, $class;
	$self->init( %param );
	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};
}

package Bird;
use strict;
use warnings;
use base qw/Initialiazable/;

sub init {
	my($self, %param ) = @_;
	$self->{_wing}  = $param{wing};
}

package Bat;
use strict;
use warnings;
use base qw/Initialiazable Beast Bird/;

sub init {
	my ( $self, %param ) = @_;
	
	$self->Beast::init( fur => $param{fur}, fang => $param{fang} );
	$self->Bird::init( wing => $param{wing} );
	
	return $self;
}

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

だいぶすっきりしました。このInitialiazableはDamian Conway "Object Oriented Perl" からの引用です。この本はかなりの名著だと思いますので興味のある方はぜひ。

※擬似ハッシュなどPBPでは非推奨になっている箇所もありますが・・・

お疲れさまでした。
次回はコアモジュールのNext.pmについて解説したいと思います。