オブジェクト編その5-継承

※この記事を書いているのは未熟なプログラマです。もし「こんな内容の記事書くんじゃない。未熟者めが!」というご意見があったらありがたく頂戴します。よろしくお願いします。

継承するには「メソッドを引き継げれば」いいんじゃない?

今回は以下のパターンを説明します。

  • メソッドがなかったら親クラスのメソッドを探す
  • 親クラスのメソッドを上書きする。
  • 親クラスのメソッドを実行しつつ、新たな動作を加える

メソッドがなかったら親クラスのメソッドを探す

Perlオブジェクト指向を説明する前にまずメソッド矢印の特徴を補足したいと思います。
次のスクリプトを記述して、実行してみてください。

C:\work\exaple\oop_005.pl

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

my $dog = Dog->new;

$dog->speak;
Dog::speak();

package Dog;
use strict;
use warnings;

sub new { bless {}, $_[0] }
sub speak { print "Bow Wow\n" };

最初のprintは、犬(オブジェクト)が話す(振舞い)という考え方を表しています。
次のprintはただ単に犬という名前空間にあるメソッドを呼び出しています。

次に、C:\work\exaple\oop_005.plを以下のように修正して下さい。

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

my $dog = Dog->new;

$dog->speak;
Dog::speak();

package Animal;
use strict;
use warnings;

sub speak { print "Say Something\n" };

package Dog;
use strict;
use warnings;
use base qw/Animal/;

犬のオブジェクトは親のAnimalクラスのspeakメソッドを継承していますので、Dogクラスにspeakメソッドがない場合は
親クラスにspeakメソッドがあるかどうかをチェックしています。同様にnewメソッドです。

それに対してDog::sepak の場合はDogクラスにメソッドがないので「speakメソッドが定義されていない」というエラーが検出されています。

矢印メソッドの場合は第一引数に自分自身を渡す性質と、そのクラスにメソッドが存在しない場合は親クラスにメソッドが存在してるかどうかを探してくれます。

親クラスのメソッドを上書きする。

今先ほどのスクリプトを修正し、実行してみてください。
修正箇所はDogクラスにspeakメソッドを追加しています。

C:\work\exaple\oop_005.pl

動物クラスのspeakメソッドは呼び出されずに済みました。

この挙動を利用して、動物クラスを継承しているクラスには必ずspeakメソッドを実装する必要があるという設計を考えます。

C:\work\exaple\oop_005.pl を次のように修正して下さい。

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

my $dog = Dog->new;
my $cat = Cat->new;

$dog->speak;
$cat->speak;

package Animal;
use strict;
use warnings;
use Carp;

sub new { bless {}, $_[0] };
sub speak { Carp::croak("動物クラスはspeakメソッドを実装する必要があります。") };

package Dog;
use strict;
use warnings;
use base qw/Animal/;

sub speak { print "Bow wow\n" };

package Cat;
use strict;
use warnings;
use base qw/Animal/;

注目してほしいのはCatクラスでspeakメソッドを定義し忘れる、つまりクラスの設計者が意図していないプログラムを書かせないように規制できていることです。動物がそもそも話すのかとか、音を出せない動物もいるかではなく。

ここで動物クラスから実際には直接オブジェクトを生成するようには設計していません。これは抽象的なクラスという考え方をしています。

親クラスのメソッドを実行しつつ、新たな動作を加える

最後にSUPERです。

突然意味不明な文章でした。意味不明なので実際にスクリプトを書いてみましょう。

C:\work\exaple\oop_005.pl を次のように修正して下さい。

修正箇所はsepakメソッドは動物クラスで定義して、鳴き声をsoundメソッドで別途定義するようにしました。
このsoundクラスは必須項目とします。ねずみの泣き声だけは小さいのでちょっと補足するために、speakメソッドにプラスアルファを追加しました。

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

my $dog   = Dog->new;
my $cat   = Cat->new;
my $mouse = Mouse->new;

$dog->speak;
$cat->speak;
$mouse->speak;

package Animal;
use strict;
use warnings;
use Carp;

sub new { bless {}, $_[0] };
sub sound { Carp::croak("動物クラスを継承した場合、そのクラスはsoundメソッドを実装する必要があります。") };
sub speak { print $_[0]->sound, "\n" };

package Dog;
use strict;
use warnings;
use base qw/Animal/;

sub sound { 'Bow wow' }

package Cat;
use strict;
use warnings;
use base qw/Animal/;

sub sound { 'meow' }

package Mouse;
use strict;
use warnings;
use base qw/Animal/;

sub sound { 'squeak' };
sub speak { 
	my $self = shift;
	$self->Animal::speak; 
	print "[ねずみの声は小さい]";
}

あと一息です。
Mouseクラスを書き換えてもう一度実行しましょう。実行結果は先ほどと変わりません。

package Mouse;
use strict;
use warnings;
use base qw/Animal/;

sub sound { 'squeak' };
sub speak { 
	my $self = shift;
	$self->SUPER::speak; 
	print "[ねずみの声は小さい]";
}

Animalクラスを直接呼び出すのではなく、Superクラスを呼び出すように変更しました。スーパークラスというものは@ISAの中に入っているクラスのことです。@ISAとは何でしょうか?最後にMouseクラスを次のように書き換えて実行してみてください。

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

use base qw/Animal/;

sub sound { 'squeak' };
sub speak { 
	my $self = shift;
	$self->Animal::speak; 
	print "[ねずみの声は小さい]\n";
	print @ISA;
}

実行結果を確認すると@ISAに'Animal'が入っています。use base で指定したスーパークラス(親クラス)が@ISAに格納されていました。矢印メソッドで呼び出したメソッドが下層クラスに存在していなかったり、SUPERが指定されている場合は、この@ISAを見て順番にメソッドが存在していないかを探しています。

お疲れさまでした。

次回は@ISAによる多重継承を説明したいと思います。