2011年4月17日日曜日

菱形継承問題をもう少し詳しく考えてみた。

以前に書いたエントリーで、Scalaにおける菱形継承問題を取り上げた。

Scalaでは、トレイトを用いて菱形継承関係を作った場合、クラス定義の際の記述の順番によって最終的な具象クラスの振る舞いが決まる、ということだった。

つまり、

"class MyPet extends DogTrait with CatTrait" と書けばMyPetはの振る舞いを優先し、
"class MyPet extends CatTrait with DogTrait" と書けばMyPetはの振る舞いを優先する。

ここで一つ問題が生じる。MyPetの状態によって、犬と猫の振る舞いを使い分けたい場合はどうすれば良いのか。

というか、そもそもそんなシチュエーションが存在するのだろうか。

これは、菱形継承の根本に関わるような気がしていて、いろいろ自分なりに考えた結果、こういう考えで良いのではないか、という結論に至ったのでちょっと書いてみたいと思う。

異論、反論は全然OKなので、「お前、そりゃ考えが足りんよ」などのご意見があれば是非いただきたい。

■新しいマジンガーを作ってみよう。

今回は概念のお話なので、具体的なコードは書かない。
そこで、今回の概念を表すクラス図をご覧いただこう。

僕はこれから新しいマジンガーを作りたい。

新しいマジンガーは、マジンガーZとグレートマジンガー、両方の性質を備えさせたいので、マジンガーZとグレートマジンガーを多重継承しようと思う。これら二つのクラスは、そもそもスーパーロボットからの派生である。

スーパーロボットといえば空を飛ばねばならない。しかし、ロボットによって飛び方は様々なので、スーパーロボットクラスにおける「空を飛ぶ」という振る舞いは抽象メソッドであり、それぞれの具象クラスで具体的に実装されている。

マジンガーZは、ジェットスクランダーという外部装置を使って空を飛ぶ。
グレートマジンガーは、内蔵されているスクランブルダッシュという装置を使って空を飛ぶ。

さて、ここで新しいマジンガーを見てみよう。

我らが新しいマジンガーは、「ブレストファイヤー」「サンダー・ブレイク」という二種類の必殺技を使うことができる。これぞ多重継承の醍醐味。その辺の悪役ロボットは一瞬にして倒せてしまいそうだ。独自の必殺技メソッドをさらに追加実装しても良いかもしれない。

さて、ここで皆さんお分かりのとおり、問題が生じる。

新しいマジンガーは、どうやって空を飛ぶのか。つまり、ジェットスクランダーを使うのか、スクランブルダッシュを使うのか、という問題だ。
これが菱形継承における曖昧さの問題である。

この曖昧さに対しては、以下のアプローチがあると思う。

・ジェットスクランダーを使って飛びたい。
→class 新しいマジンガー extends グレートマジンガー with マジンガーZと書け!

・スクランブルダッシュを使って飛びたい。
→class 新しいマジンガー extends マジンガーZ with グレートマジンガーと書け!

・新しい方式で飛びたい
→「空を飛ぶ」メソッドをオーバーライドしろ!

・状況に応じてジェットスクランダーとスクランブルダッシュを使い分けたい。
→そもそも、要求として「空を飛ぶ」という振る舞いが共通の意味を持たなくなっているのだから、マジンガーZとグレートマジンガー双方にて「ジェットスクランダーで飛ぶ」「スクランブルダッシュで飛ぶ」というようにメソッドをリファクタリングしろ!

こういう事で良いのではないだろうか?

ここでのポイントは、最後の「使い分けたい場合どうすれば良いか」という部分である。
使い分けたい、という要求が発生した時点で、「空を飛ぶ」というメソッドはそれぞれのクラスにおいて共通の意味を持たなくなっている。つまり、「どのように飛べば良いのか」、という新しい意味付けがメソッドに対して行われていることになる。
それはつまり、継承ではなくなっているという事だ。

ちなみに、我らが新しいマジンガーは、オーバーライドして反重力エンジンを用いた、翼の不要な飛び方を実装してみようと思う。

と、ここまで書いておいて一つ大きな問題に気づいた。

上記クラス図に、「歩く」というメソッドを追加したモデルで考えみよう。

新しいマジンガーに対して、「マジンガーZのように歩き、グレートマジンガーのように飛べ」という要求があった場合どうすれば良いのだろう……。

適切なモデリングが思い浮かばないので、ちょっと宿題とさせていただきたい…。

0 件のコメント:

コメントを投稿