継承かコンポジションか?

when-inheritance.png

継承してもいいのは内包のときだけ!

継承には次の 3 種類が存在する.

  • 内包
  • 外延
  • 機能
種類 説明 継承?コンポジション?
内包 共通の性質である. 子が 親を内包的に継承している場合, 子 は 親の性質を持っており, 子 は 親 の性質を狭めた性質を持っている. このときの B, 親の関係が, オブジェクト指向の説明で登場する, いわゆる is-a 関係である(この場合, 子 is a 親). たとえば, 親 が「乗り物」, 子が「車」という場合である. 継承ok
外延 「具体的な物」またはその集合である. B が A を外延的に継承する場合, B は A のうち, ある性質を持つものの集合である. 内包と異なるのは, 具体的な物の集合で定義されているために, あるものが B であるかという質問に, 明確に回答が出せる点である. 内包では, あくまで性質による定義なので, 中間的な性質を持つ場合など, 回答が出せないこともある. たとえば, A が「乗り物」, B が「個人所有の乗り物」という場合, B は A のうち個人所有のものを具体的に列挙した集合なので, A の外延的継承と言うことができる. use composition
機能 A があるときに, B は「機能が追加された A」である. 追加されるだけでなく, 変更, 削除されている場合もある. 内包による継承と異なる点は, たとえば, B が A を継承する場合, 内包では B は必ず A の性質と同じ性質を持っているのに対し, 機能継承では異なる性質を持っていることがある(B is a A でなくなっている), という点である. たとえば A が「乗り物」, B が「乗り物兼住居」のような場合である. use composition
まとめ

いいソフトを作るのであれば, 「内包」のときのみ継承を使うべきであり,
「外延」「機能」については, 継承ではなくオブジェクトコンポジション等を使うべきである.

  • 親クラスを作るならば、なるべく抽象的にする(作るときは慎重に!)
  • 具体的なクラスを作ったら、他の人が継承しないようにクラスにfinalの修飾子をつける

あるクラスから派生させる強い必然性が無い限り、仮想化は避けるべき。[3]
仮想化するとパフォーマンスが下がるし、未来は予測できないから。

  • 継承によって新たに作るクラスは、もとのクラスよりも具体的なものにする

継承した時のバグ的な注意

子クラスで全く同じ名前の変数を作ったら親クラスの変数は無視される。
ややこしいから親クラスと同じ名前の変数なんか作らないほうがいいね。

継承に向いているもの

  • クラスの振る舞いを変更したいとき[3]

むしろ、こういう時だけ継承するべきといっても過言じゃない。

私的には、、

  • 引数を汎用的にしたいとき

親クラスを引数にしておけば、柔軟に対応できる、という点で存在価値があると思う。
しかし、問題発生。
コンテナに入れると、これが使えないのだ。

void function(list<Parent> _p){}
...
list<Child> c;
function(c);//ダメ!

継承化にすべきサイン

Effective Jave[2]の項目20:タグ付けクラスよりクラス階層を選ぶによると、
クラスの中にmTypeみたいな変数を持っていて、そのmTypeによって処理が分岐するような場合は、継承化にしたほうがよいサインです

継承に向いていないもの

  1. すべての型に共通していない要素はつけないこと[Bibliography item headfirst not found.]
  2. 継承しようとしたけど、データだけしか変化おらず、振る舞いは変わっていない場合。[3]
  3. 1フレームあたり何千回も呼び出される内部ループの深いところにある関数だった場合[3]

3.毎フレーム呼び出されるものを仮想化したいときは、関数の数が少なくなるように工夫すべき

サポートサイト Wikidot.com