継承を使ったポリモーフィズム




  1. Perl




  2. オブジェクト指向



  3. ポリモーフィズム

 オブジェクト指向のひとつの特徴にポリモーフィズム(多態性)があります。ポリモーフィズムは、同じように見える記述の振る舞いが異なるという意味で捉えておけばよいでしょう。

1. クラスによって異なる振る舞いをさせる

 では、自分の属するクラスによって異なった動きをするサンプルを書いてみます。簡単な例として、特定の記号で文字列を連結するということをします。Unixの区切り文字と、Windowsの区切り文字をサンプルとして扱います。

use strict;
use warnings;

my $file_path_unix = FilePath::Unix->new;
my $file_path_win = FilePath::Windows->new;

my $unix_result = $file_path_unix->cat_path('a', 'b');
my $win_result = $file_path_win->cat_path('a', 'b');

print "unix: $unix_result\n";
print "win: $win_result\n";

# ファイルパスを扱うための基底クラス
package FilePath::Base;
sub new {
  my $proto = shift;
  my $class = ref $proto || $proto;
  my $self = {};
  bless $self, $class;
  return $self;
}

sub cat_path {
  my ($self, $str1, $str2) = @_;
  return $str1 . $self->delimiter . $str2;
}


# Unix 用
package FilePath::Unix;
use base 'FilePath::Base';

# Unixの場合の区切り文字
sub delimiter { return '/' }

# Windows用
package FilePath::Windows;
use base 'FilePath::Base';

# Windowsの場合の区切り文字
sub delimiter { return '\\' }

 このサンプルを実行すると

unix: a/b
win: a\b

と表示されます。

2. cat_pathの振る舞いに注目

 まずこのサンプルで特徴的なことは、cat_pathというメソッドがふたつの意味を持つということです。

package FilePath::Base;
sub new { ... }

sub cat_path {
  my ($self, $str1, $str2) = @_;
  return $str1 . $self->delimiter . $str2;
}

 FilePath::Base というクラスを見ると、cat_path というメソッドがあります。ところで、cat_pathを呼び出している部分を見ると、File::Pathクラスのオブジェクトが生成されているわけではありません。

my $file_path_unix = FilePath::Unix->new;
my $file_path_win = FilePath::Windows->new;

my $unix_result = $file_path_unix->cat_path('a', 'b');
my $win_result = $file_path_win->cat_path('a', 'b');

 それぞれ、FilePath::Unixクラス と FilePath::Windowsクラスのオブジェクトが生成されています。

 これは継承のところで解説したように、FilePath::Unix と FilePath::Windows が FilePath::Base を継承しているので、FilePath::Baseクラスが持つメソッド、cat_path を呼び出すことができるのでした。

 次に注目してほしいところは、cat_pathの中で、$self->delimiter という記述の部分です。

3. スーバークラスでサブクラスのメソッドを呼び出す。

 cat_pathの中に注目してみましょう。

sub cat_path {
  my ($self, $str1, $str2) = @_;
  return $str1 . $self->delimiter . $str2;
}

 cat_pathの中で、delimiterメソッドが呼ばれています。けれども、FilePath::Baseは、delimiterというメソッドをもっていません。自分が持っていないメソッドを呼び出すことができるのでしょうか?

 実はこの $self の部分に秘密があります。

my $unix_result = $file_path_unix->cat_path('a', 'b');

のようにcat_pathメソッドを呼び出したときに、$self に代入されるのは、FilePath::Unix クラスのオブジェクとになります。

 FilePath::Unixクラスは delimiter メソッドを持っているので、cat_path メソッドの中から、delimiter メソッドを呼び出すことができます。

4. FilePath::Base クラスは継承されることを予定した不完全なクラスである。

 FilePath::Base クラスのオブジェクトを作成しても、cat_file は内部でdelimiter メソッドを呼び出そうとしても失敗します。

my $file_path_base = FilePath::Base->new;
$file_path_base->cat_file( 'a', 'b' ); # 失敗!

 FilePath::Base クラスを継承したdelimiterメソッドを実装したクラスが作って意味を持つものになります。

 このようなクラスを、継承予定クラスと名づけたい。( Perlの本を読んでもこのようなクラスを適切に命名した名前が発見できなかったので、継承予定クラスと呼ぶことにします。オブジェクトを生成できるのでJavaの抽象クラスというわけでもないので)

5. サブクラスでのメソッドの定義

 そしてポリモーフィズムを実現するために、FilePath::UnixクラスとFilePath::Windows クラスで、delimiterメソッドの実装が異なっていることに注目してください。

# FilePath::Unixクラスの場合
sub delimiter{ return '/' }
# FilePath::Windowsクラスの場合
sub delimiter{ return '\\' }

 この記述が、出力結果の振る舞いを実質的に変えている部分です。

unix: a/b
win: a\b