「オブジェクト指向設計実践ガイド」 第8章 コンポジションでオブジェクトを組み合わせる

第8章 コンポジションでオブジェクトを組み合わせる

単一の責任を持ったオブジェクトを複雑な全体へと組み合わせることによりアプリケーションは作られる。 より大きい部分が部品を持つという関係でオブジェクトは関連付けられる。そして、それぞれはインターフェースを介してメッセージを送り合う。

コンポジション

継承は IS-A という関係を作るが、コンポジションは HAS-A という関係を作る。「マウンテンバイクは自転車である」が IS-A 「自転車はタイヤを持つ」が HAS-A

自転車オブジェクトが持っているタイヤオブジェクトやチェーンオブジェクト、ハンドルオブジェクトを取り替えられる設計になっていれば、自転車クラスの変更無しでマウンテンバイクやロードバイクを表現可能になる。これはロードバイクやマウンテンバイクといった特化された自転車のクラスを自転車クラスを継承することによって作ることと対立する手法である。

ロードバイクとマウンテンバイクを同じ「自転車」としてみて、共有する振る舞いをまとめることができるのは継承もコンポジションも同じ。だが、それぞれ使い所が異なる。

コンポジションと継承、それぞれの特徴

継承

「オブジェクトを階層構造に構成する必要があるが、メッセージの委譲は自動的に行われる」

コンポジション

「明示的にメッセージ委譲を定義する必要があるが、オブジェクトを複雑な構造の下に置かなくてすむ」

というそれぞれ反対の性質を持っている。

一般的には、はっきりとした継承を使うべき理由がないときはコンポジションを使うべきである。なぜなら、コンポジションのほうが依存関係を小さくすることができるからである。

継承のメリットとデメリット

うまく構築された継承の階層構造は「合理性」「再利用性」「模範性」に優れている。

  • 合理的: 階層の上の方を変更することで、小さな変更を継承ツリーの下の方まで波及されられる。必要な大変更をわずかなコードの変更で実現できる。
  • 再利用性: 階層構造は拡張には開いており(サブクラスの追加が容易で既存コードの変更がいらない)修正には閉じている(変更の影響範囲が明確)
  • 模範性: 階層構造があると、これから追加するコードが有るときどこに追加するかわかりやすい

しかしこれらのメリットはあくまで上手に階層構造が設計できた場合の話である。下手な設計は、合理性・再利用性・模範性という性質を逆転させてしまう。

  • 変更コスト(⇔合理性): わずかな変更がすべてを破壊するリスク
  • 振る舞いの変更不可能(⇔再利用性): 既存の階層にうまくあてはまらないクラスの置き場所がない
  • 混沌(⇔模範性): どこに何があるのかわからないコード

継承は深い依存関係の集まりを伴っていることに注意を払う必要がある。RubyではどのクラスもObjectクラスから継承継承とつづくツリーの下にある。

コンポジションのメリットとデメリット

コンポジションを使うことのメリットには次のようなものがある。

  • 単一責任で、継承しているものが少ない「見通しの良い」オブジェクトがつくりやすい
  • インターフェースが定義されており、既存の部品の亜種を新しく作るのが容易

コンポジションによって成るアプリケーションの部品(オブジェクト)は小さく、独立し、インターフェースを持つ。なので交換可能なコンポーネント化しやすい。

一方で、部品が小さくなるということは全体として大量の部品が現れるということになる。ひとつひとつが見通しよくても、それがたくさんあった場合には把握が難しくなる。

どちらを選ぶべきか

  • 継承とは特殊化です
  • 継承が適しているのは、過去のコードの大部分を使いつつ、新たなコードの追加が比較的少量の時に、既存のクラスに機能を追加する場合です。
  • 振る舞いが、それを構成するパーツの総和を上回るのなら、コンポジションを使いましょう