第53回 ポリモーフィズムの考え方で複数のクラスを作成してみよう

 クラスを使ったプログラミングをする際に,「ポリモーフィズム(*1)」という考え方があります。ポリモーフィズムを使用すると,複数のクラスを同じような感覚で使用できます。また,継承の仕組みと組み合わせると,関連するクラスを,一つのグループのように扱うこともできます。

「再生ボタン」を作るような心構えで「ポリモーフィズム」をとらえてみよう

 今回は「ポリモーフィズム」という仕組みを使ってクラスを作成してみましょう。ポリモーフィズムは「多態性」「多様性」などと訳される考え方です。解釈には幅があるのですが,コードを書く側の意見としては,「複数クラスを作成する際には,同じ『名前』のプロパティやメソッドで作っておくと,使う側が割と直観的にその機能を使うことが出来て便利ですよ。でも,処理の中身はそれぞれ変えちゃってもOKです」という仕組みです。

図1: ポリモーフィズムのイメージ(クリックするとムービーを表示します)

 とかく「作る側」の立場の人は,自分の作る機能に夢中になってしまい,結果としてできあがったものが「使う側」にとってはわかりにくいものとなってしまうことが割とあります。

 筆者が考える,現実の世界でのポリモーフィズムの考え方に近い例は,「再生ボタン」です(*2)。CDプレーヤーでも,DVDデッキでも,ステレオでも,iPodでも,「何かを再生するボタン」と言えば「右向き三角」ボタンですよね。私たちはこの「右向き三角」を見れば,「あ,これはたぶん押すと再生するな。」と直観的にわかるわけです。実際に再生するのは,CDやDVD,ハードディスク内のデータなど,違うものを違う技術で再生しているのですけれども,我々はその振る舞いの違いを意識することなく,再生ボタンを使うことができますよね。

 これが作る側のエキセントリックな感覚でスイカのイラストにでもなっていたら,いったいこれが何のボタンなのかわかりません。さらに,そのイラストがメーカーや機材ごとに変わっていたら,もう何を押せば再生してくれるのかわけがわからなくなってしまいます。

 そんな無用な混乱を避けて,利用者にやさしい「右向き三角」ボタンを用意する。それがポリモーフィズムを考えるときの,一つのヒントとなります。

三つのクラスを作成してみよう

 では,今回も簡単なカードゲームを作りながら,ActionScript2.0でのポリモーフィズムを使ったクラスの作成手順を見てみましょう。

 今回作成するカードゲームは「魔法使いカード」「戦士カード」「シェフカード」の3種類のカードを用意します。それぞれのカードはトランプのように1~13の数値をランダムで持ち,その数字の大きさによって「攻撃」を行うものとします。まずは,それぞれのカードの「攻撃」処理を行うためのメソッド名を,見た目にわかりやすいように次のようにしてみました(表1)。

表1: 三つのクラスの「攻撃」をするメソッド名
カード「攻撃」するためのメソッド名
MagicianCardクラスcastASpell()
FighterCardクラスattackWithSword()
ChefCardクラスcookAFrenchDish()

 ひと目でどんな処理をしたいのかが連想できるメソッド名です。よく使う英単語の組み合わせなので,コードを書く時にスペルミスもしにくくなりますね(*3)。

 上記のメソッド名を使って三つのクラスを定義すると,次のようになります(*4)。

図2: 三つのクラスの定義

 さて,それではこの3種類のクラスを使って,カードを5枚作成し,「攻撃」の処理を実行してみましょう。

 三つのクラスと同じディレクトリ内に新規Flashドキュメントを作成し,次のようにコードを記述します(リスト1)。

リスト1: 三つのクラスの「攻撃」を行うコード

import MagicianCard;
import FighterCard;
import ChefCard;
 
//クラスへの参照の配列を作成
var cardClassList:Array = [MagicianCard,FighterCard,ChefCard];
//カードをまとめる配列を準備
var cardList:Array = new Array();
 
//5枚のカードを準備
var i:Number,r:Number;
for(i=0;i<5;i++){
  //3種類のカードのうち,ランダムで一つを作成し,リストに加える
  r = Math.random()*cardClassList.length>>0;
  cardList.push(new cardClassList[r]());
}
//5枚のカードの「攻撃」処理を実行
var damage:Number;
for(var i:Number = 0;i<cardList.length;i++){
  //instanceof演算子でカードの種類を判定し,それぞれに応じたメソッドを実行
  if(cardList[i] instanceof MagicianCard){
    damage = cardList[i].castASpell();
  }else if(cardList[i] instanceof FighterCard){
    damage = cardList[i].attackWithSword();
  }else if(cardList[i] instanceof ChefCard){
    damage = cardList[i].cookAFrenchDish();
  }
  trace(damage + "ポイントのダメージを受けた!\n---")
}

 コードの実行結果は,図3のようになります。それぞれのクラスに定義した攻撃処理が実行されていることが確認できますね。

図3: コードの実行結果

 ここで注目していただきたいのは,クラスの種類に応じて,「攻撃」処理を呼び出している個所です。ActionScript2.0では,「instanceof演算子」で,任意のインスタンスが,指定したクラスのインスタンスであるかどうかを判定できます。

 そこで,3種類のカードのクラスである「MagicianCard」「FighterCard」「ChefCard」それぞれについて判定を行い,クラスの種類に応じて「castASpellメソッド」「attackWithSwordメソッド」「cookAFrenchDishメソッド」を実行しています。

 この方法でも良いのですが,なんだかちょっとスッキッリしませんよね。今はカードの種類は3種類ですけれども,将来的には10種類になるかもしれません。さらに,Chefクラスでは「cookAChineseDishメソッド」や「cookAJapaneseDishメソッド」も増やしたい場合だってあるでしょう。もしそうなったとしたら,ifステートメントでの分岐をその都度増やさなくてはいけません。ちょっと面倒な話です。

 単体のクラスのみに注目するのならば,わかりやすいはずのメソッド名なのですが,三つのクラスをまとめて扱うことを考えると,クラスごとに「攻撃」処理のメソッド名が違うのはちょっと考えものです。そこで,「ポリモーフィズム」の考え方を使ってみましょう。三つのクラスの「攻撃」処理は,「attackメソッド」という名前のメソッドで統一することにしてみます。

 まずはMagicianCardクラスとFighterCardクラスは,次のようにメソッド名を変更してしまいました(図4)。

図4: attackメソッドに作り変える

 そして,Chefクラスでは,せっかくわかりやすいメソッド名を付けたのですから,cookAFrenchDishメソッドは,そのままプライベート・メソッドとして残しておき,別途,attackメソッドを追加しました。そして,attackメソッドを実行すると,cookAFrenchDishメソッドを呼び出すように変更をする作戦で作り変えることにしましょう。

 また,せっかくattackメソッドを別途作成したのに,単にcookAFrenchDishメソッドの戻り値を右から左へと返すのはつまらないので,ついでに「cookAJapaneseDishメソッド」も作成し,attackメソッドを実行すると,二つのメソッドのうち,どちらかの戻り値を返すように変更してみました(図5)(*5)。

図5: attackメソッドを追加する

 どちらの修正方法でも共通しているのは「attackメソッドを用意して,攻撃の処理はそこで行う」という共通ルールを守っている点です。この「同じ名前のメソッドを用意する」というのがポリモーフィズムの考え方となります。

 では,attackメソッドを使用して,先ほどの攻撃処理を書きなおしてみるとどうなるでしょうか?(リスト2

リスト2: attackメソッドを使った攻撃処理に書き換える

//5枚のカードの「攻撃」処理を実行
var damage:Number = 0;
for(var i:Number = 0;i<cardList.length;i++){
  //どのカードでも常にattackメソッドで攻撃処理ができる
  damage = cardList[i].attack();
  trace(damage + "ポイントのダメージを受けた!\n---");
}

 ずいぶんとスッキリとした記述にできましたね。三つのクラスの攻撃処理をするメソッド名が,すべて「attackメソッド」で同じなので,ifステートメントで処理を分岐する必要がありません。これなら,どれだけカードの種類を増やしても「攻撃処理の名前は『attackメソッド』」というルールを守っていれば,このままのコードで対処できます。

 実行結果を見てみると,各クラスのattackメソッドがきちんと実行されていることも確認できますね(図6)。

図6: 実行結果

 このように,ポリモーフィズムの考え方を使って「意識的に同じ名前のメソッド名を関連するクラスに用意する」ことにより,複数のクラスを組み合わせて使った際の処理が簡単になります。

 また,「同じ名前」を用意することで,過去にその「名前」のメソッドをどこかのクラスで利用したことのある人にとっては「あ,たぶんこの名前だと,ああなるんだろうな」と,処理の内容が容易に想像できます(*6)。クラスを使う側の気持ちを考えた,思いやりのある仕組みですね。