今回は継承についてより深く学びます。特になぜ多重継承が必要なのか,多重継承にはどのような問題があり,JavaやRubyがどのように解決しているのかを学びます。
前回はオブジェクト指向プログラミングを構成する3原則(ポリモーフィズム,データ抽象,継承)のうち,継承について扱いました。人が一度に把握,記憶できる概念の数には限りがあること,これを解決するため,クラスのうち似たような部分をくくり出す仕組み(継承)が必要であることを解説しました。継承はプログラムの構造化,抽象化の流れが自然に進化した形として登場したとも説明しました。
しかし,最後の部分は厳密には正しくありません。構造化,抽象化とは,共通部分をスーパークラス(親クラス)としてくくり出すボトムアップ・アプローチを意味します。このように継承が誕生したのであれば,最初から複数のスーパークラスを持つことができる多重継承*1が主流だったに違いないからです。
しかし,実際には継承を組み込んだ最初のプログラミング言語Simulaは,単一継承のみを提供していました。その後の多くのオブジェクト指向言語でも同じです。このため,継承の元々の目的は実際には段階的な詳細化であったと思われます。
なぜ多重継承が必要なのか
単一継承はスーパークラスを1つしか持てません。これでは制限が厳しすぎると感じることもあります。現実世界では,人はしばしば会社員であると同時に父親であったり,プログラマであると同時にライターであったりします。
先月説明したように,継承をプログラムの共通部分をくくり出す抽象化の手段として考えてみると,1つのクラスから抽象化(抽出)できる部分が1つだけというのはプログラミングの上で大きな制約になります。このような発想から生まれたのが多重継承です。単一継承と多重継承の違いはスーパークラスの数だけですから,多重継承は単純継承の完全なスーパーセットであり,自然な拡張と考えることができます(図1[拡大表示])。
多重継承が使える言語は,単一継承にある不自然な制約を受けません。例えば,単一継承のみを提供する言語Smalltalkの例で考えてみましょう。Smalltalkのクラス・ライブラリには単一継承によって不自然な形になっています。
Smalltalkでは,入出力を担うStreamクラスに3つのサブクラスがあります。入力用のReadStream,出力用のWriteStream,双方向入出力向けのReadWriteStreamです。ReadWriteStreamはReadStreamの機能とWriteStreamの機能の両方を持ちます。しかし,Smalltalkは単一継承だけしか使えませんから,両方のクラスから継承することはできません。
結局,ReadWriteStreamをWriteStreamのサブクラスにした上で,ReadStreamのコードをコピーし,ReadWriteStreamを実現しています(図2[拡大表示])。プログラムのメンテナンスという観点から,コードのコピーは避けるべきです。言語の制約からコードをコピーが必要になるというのは望ましくありません。
一方,多重継承があれば,自然な発想に基づいてReadStreamクラスとWriteStreamクラスの両方を継承したReadWriteStreamを作れます(図3[拡大表示])。
多重継承と単一継承は表裏一体
このように多重継承と単一継承を比べて見ると,単一継承の特徴が際立ちます。
●継承関係が単純
単一継承では,継承関係が単純な木構造になります。これは利点でもあり,欠点でもあります。クラスの関係が単純なため混乱を生みませんし,実装も簡単です。しかし,先ほどのSmalltalkのStreamのように,継承関係を越えたコードの共有ができず,コピーが必要になる場合があります。
式や変数に型指定が行われるJavaのような静的型の言語においては,単一継承からくるもう一つの欠点も見えてきますが,これは後で説明しましょう。
多重継承の特徴はちょうど逆です。多重継承には2つの優れた特性があります。
●単一継承の自然な拡張
●複数のクラスから機能を取り込むことができる
多重継承は単一継承ができることなら何でも実現できます。しかし,クラスの関係が複雑になりがちという欠点があります。
goto文と多重継承は似ている
前回,構造化プログラミングについて学びました。gotoを用いて任意の場所にジャンプできる構造よりも,分岐と繰り返しという制限された構造によってプログラムを構築した方が望ましいと説明しました。分岐や繰り返しはgotoを使って実現できますし,分岐などでは直接実現できない制御の流れもgotoなら記述できます。gotoは分岐などよりも「記述力が高い」ということができます。
しかし,gotoは機能が高いにもかかわらず望ましくないと言われます。gotoを使ったプログラムは,制御の流れがどこに移るのか一目で分からないことが多く,人間にとって理解しにくいプログラムになりやすいのです。このような制御が絡み合ったプログラムのことを「スパゲッティ・プログラム」と呼びます。
多重継承についても同じことが言えます。多重継承は単一継承の拡張ですから,単一継承を使ってできることはすべて実現できます。単一継承では解決が難しい問題も解決できるでしょう。
しかし,複数のスーパークラスからの継承を許すことで,クラスの関係が複雑なネットワーク構造になります(単一継承は木構造)。このため,どのクラスがどのクラスの機能を利用しているのか分かりにくくなりますし,問題が発生したとき,どのクラスとどのクラスが悪さをしているか見分けにくくなります。
このような絡み合った継承のことを俗に「スパゲッティ継承」と呼ぶことがあります。もちろん,多重継承があるからといって,必ずスパゲッティ継承になるわけではありませんが,注意が必要なのは確かです。