- 継承をより完全なものにするには複数の親を持てるように拡張する必要がある
- 抽象化することが目的
- 大学システムのモデルにおけるTAの例(生徒であると同時に先生でもある)は多重継承の入門例としてはふさわしくない。
- 正確なグラフを書くと反復継承(いわゆるダイヤモンド継承)にあたる
- 多重継承に習熟してから反復継承は学ぶべき
- 一般的な多重継承であれば衝突は 一切 発生しない(衝突が発生することが前提であると捉えない)
社用飛行機モデルを多重継承で表現するのは典型的な例だ。
- PLANEクラス(飛行機モデル。飛行機を操作したり、クエリを持っている)
- ASSETクラス(資産モデル。会社の資産を扱う)
の2つのクラスを継承することで表現できる。
class COMPANY_PLANE inherit
PLANE
ASSET
feature
-- 社用機に特有の特性
end
他の例として、
- 腕時計
- 水陸両用車
- 食堂車
- ソファーベッド
- トレーラハウス
等。
- カーネルライブラリクラスの幾つか(例えばINTEGER、REAL、DOUBLE)には算術的な特性(値、操作)が必要
また、アプリケーション特有のクラス(行列計算のためのMATRIXクラス)でも同様な算術計算が欲しい。
- そういった算術特性を暫定クラスNUMERICを使う
- 順序特性(任意の要素を比較するための特性)が欲しいクラスもある
- STRINGや、アプリケーションクラスでも役に立つ
- 暫定クラスCOMPARABLEを使う
- COMPARABLEのすべての子孫がNUMERICの子孫というわけではない
- 逆にNUMERICの子孫がCOMPARABLEというわけではない
- 比較可能であると同時に数値でもあるクラスはNUMERIC、COMPARABLEを継承する
ウィンドウシステムは、
- 階層構造のメンバとしてウィンドウを扱い
- 図形オブジェクトである
の特性がある。 これらの特性を抽象化する。
- 階層構造は、TREE
- 長方形オブジェクトは、RECTANGLE
class WINDOW inherit
TREE[WINDOW]
RECTANGLE
feature
-- 特定のウィンドウ特性
end
- TREEは総称クラスとなる(Generics。Java語でいうとTree)
- 総称クラスであるTREEもまた多重継承で表現できる
- リスト(LIST)の特性(この数を調べる、子を追加する、子を削除する等)を持ち
- リストの要素(CELL)の特性(兄弟を取り出す、親にアタッチする等)を持つ
class TREE[G] inherit
LIST[G]
CELL[G]
feature
-- TREE特有の特性
end
- 単一の図形を組み合わせたものを表現する[COMPOSITE_FIGURE]
- 単一の図形[FIGURE]
- これもまたあらゆる特性をもったクラスを継承している
- 図形をまとめるデータ構造[LINKED_LIST]
- 単一の図形[FIGURE]
class COMPOSITE_FIGURE inherit
FIGURE
LINKED_LIST[FIGURE]
feature
-- ...
end
- 図形を表示するオペレータ
display
やrotate
、translate
といったCOMPOSITE_FIGURE
の特性それぞれは順番にFIGURE
を操作する- 「イテレータ」クラスをさらに作って抽象化可能である
- [超訳]つまるところ継承による抽象化はパワフルである、ということがいいたい
- 多重継承の重要な使い方は、暫定クラスで定義された抽象的概念を有効クラスで提供された特性を使って実装することである
- スタッククラスを配列で実装する(ARRAYED_STACK)
- STACKクラス(暫定クラス)
- ARRAYクラス(有効クラス)
- この暫定クラスの特性を、有効クラスの特性で実装するための多重継承を「政略結婚」と呼ぶ
- STACKクラスは暫定クラスだが貴族出身。富はなくとも高い名声を持つ
- ARRAYクラス有効クラスで、その富と権力に見合う名声が欲しい
- 多重継承はあるクラスに、そのクラスを表す基本の抽象的概念以外にもいくつかの性質があることを明記したいときになくてはならない技術である
- オブジェクトの構造を永久的なものにする(ストレージに保存可能)性質を実装する
- STORABLEクラスを継承することでストレージに保存可能になる
- が、それらは他のクラスを親に持っているはずであるので多重継承なしには機能しない
- クラス名が「-ABLE」で終わるものはこういった汎用的な構造の性質を表すクラス
- 「履歴」機能
- 「テスト」機能
これらの機能を使いたい時、それぞれ機能を実装しているクラスを継承する
その新しいクラス自身の特性は特に定義することはないが、継承したそれぞれの親クラスとして振る舞う 複数の親からの特性と性質を単に結合しただけ
BUTTON
クラスとHOLE
クラスを継承したBUTTON_HOLE
クラス- 右クリックするとピック&スロー機能
- 左クリックするとボタン機能
- 多重継承はパワフルだ(再)
- 多重継承ではなく、単一継承で2つの抽象的概念を結合するのは、コピー&ペーストでの複製を意味する
- 多相性、開放/閉鎖の原則、継承の再利用性がもたらすメリットをすべて失う
これは受け入れがたいことである
多重継承には、「名前の衝突」という技術的な問題が生じる。 この問題の解決方法は、特性の改名(feature renaming)である。
- クラスは親の特性全てに、明示せずにアクセス可能である
class SANTA_BARBARA inherit
LONDON
NEW_YORK
feature
-- ...
end
LONDONとNEW_YORKに同じ名前の特性foo
がある場合どうしたらいいのか?
- 親クラスであるLONDONやNEW_YORKを修正する、という手は避けるべきである
- それらのソースにはアクセスできない場合や、アクセスできても変更できない場合、変更できたとしてもバージョンアップのたびに変更するのは辛い
- 開放/閉鎖の原則(モジュールを再利用して新規に拡張する場合そのモジュールを変更してはいけない)
- 継承時に特性名を変更することで解決する
class SANTA_BARBARA inherit
LONDON
rename foo as fog end
NEW_YORK
feature
-- ...
end
- LONDONから継承した特性fooはfogとして、NEW_YORKから継承した特性fooはfooとして参照可能
- LONDONの顧客はそのままfooとして使用できる
class SANTA_BARBARA inherit
LONDON
rename foo as fog end
NEW_YORK
rename foo as zoo end
feature
-- ...
end
について効果を考える。
- 名前の衝突問題の本質は「名前は恣意的(その時々の思いつきで物事を判断する)なものである」こと
- つまり適切な名前を付けることで問題を解決できる
まさかとおもうだろうが、この問題を「奥の深い意味的問題」であるという人もいるらしい。 意味的でも意味深でもない。むしろ、単純な構文的問題である。 ローカルな文脈で、クラス作成者の一人が最初のクラスで名前fogを選び、次のクラスでzooを選んでいたならば名前の衝突はおきない。 いずれの場合も、1文字かわっただけであるが。 このように名前の衝突は不運なケースである。
- 再宣言では特性が変わるが、名前は変わらない
- 解明では名前が変わるが、特性は変わらない
継承された特性を改名できるということは、名前の衝突がない場合においても有用な手段である。
- 継承した特性は有用であるが、名前が適切でない場合(そのクラスの抽象的概念にそぐわない)
たとえば、WINDOWクラス(TREEクラスを継承)を考える。 WINDOWインスタンスに対するインタフェースとして、TREEクラスの特性の名前は適切ではない
class WINDOW inherit
TREE[WINDOW]
rename
child as subwindow, is_leaf as is_terminal, root as screen,
arity as child_count,
RECTANGLE
feature
-- ...
end
- WINDOWクラスにおける抽象的概念に合わせて改名する
- オブジェクト志向ソフトウェア構築において、名前付け(特製名だけでなくクラス名)はとても大切
- 全体的な名前をつけることは、だいたい良い手
- しかし、WINDOWクラスの例のように、それぞれのクラスの抽象的概念にそぐわないときは特性名を改名する柔軟性を持つこと
改名された特性が生成プロシージャである場合 たとえば、ARRAYED_STACKを例に考えてみる。
- プロシージャ名として標準的に
make
を使う - ARRAYは有効クラスですでに
make
が定義されている - そこでARRAYクラスの
make
をarray_make
に改名する