継承とコンポジションとアダプタ

[id:lethevert:20051003:p5]
あいかわらず、継承のことを考えています。

テクニカルタームを使って、状況をもう一度整理

オブジェクト指向プログラミングでは、クラスの再利用の方法として、「継承」と「コンポジション」と「アダプタ」の3種類があります。

継承(インヘリタンス)の分類

「継承」には、大きく2つの目的があります。

  • 設計の共有 : インターフェース
  • 実装の共有 : mixin

右に書いたのは、特別にそのためだけに使うことを目的として機能を制限した継承の種類を指しています。
通常の継承は、2つの目的のどちらのためにも使えますし、区別せずに使うこともできます。
なお、インターフェースという概念は、変数(やフィールド)に型を要求する(静的型付け)言語でのみ意味のある概念です。なぜなら、動的型付け言語では、メソッドの解決も動的に行われるので、設計の共有とは単に同名のメソッドを持っているというだけでよく、インターフェースという特別な仕組みを必要としないからです。
実装の共有には、コンポジションを使うこともできます。

コンポジションの分類

コンポジション」とは、オブジェクトが別のオブジェクトを部分として「含む」という利用の仕方です。
コンポジションは別のオブジェクトの機能(実装)を再利用するために使いますが、その方法としては、大きく2通りの方法があります。

  • 委譲
  • コンポジット(部品)オブジェクトへのアクセスメソッドを使う(これにはいい名前がない)

委譲のいいところは、コンテナ(全体)オブジェクト側で、どのメソッドをアクセス可能にするかを制御できるところです。また、コンポジットオブジェクトの仕様変更にも強くなるし、公開メソッド名をコンポジットオブジェクトのメソッド名とは別にすることもできます。
コンポジットオブジェクトそのものへのアクセスを許すやり方は、なによりも実装が簡単です。これは、特に集約オブジェクトのようなコレクションクラスを部品として持つようなクラスに適しています。また、コンポジットオブジェクトの仕様変更をしたときに、コンテナオブジェクトに修正をする必要ないことも、利点になりえます。
コンポジションは、実装の継承と似たようなことを実現することができます。ある仕様を実現したいとき、継承とコンポジションのどちらでも実現可能ならば、一般的には、コンポジションを使う方が再利用性の高いコードを書くことができます。(*1)

アダプタ(には分類はないが)

「アダプタ」とは、設計(インターフェース)の型が合わないオブジェクト同士を連携させるために使うテクニックです。再利用したいオブジェクトにアダプタを作成して、インターフェースを変換してやることで、利用側オブジェクトが要求するインターフェースに適合させます。
言語によっては、この目的のために使える、簡便な機構をもっているものもあります。このような簡便な機能を使えば、新たなアダプタクラスを作ることなく、同様の効果を生み出すことができます。(*2)

なお、アダプタは、動的型付け言語でも利用されます。

インターフェースの活用(*3)

コンポジションとインターフェースを組み合わせることで、再利用性の高いコードを書くことができます。オブジェクトが関連する別のオブジェクトに対して要求する仕様をインターフェースとして定義して、オブジェクト間の結合を弱めることで、再利用しやすくするのです。
たとえば、コンテナオブジェクトがコンポジットオブジェクトを直接持つのではなく、必要なメソッドだけを定義したインターフェースを通して持つことで、コンポジットオブジェクトの仕様変更に対する影響を少なくすることができます。特に、コンテナオブジェクトを再利用するために、コンポジットオブジェクトを別のものと取り替えたいとき、インターフェースを利用している場合のほうが、そうでない場合に比べて、より少ない手数で修正をすることができます。新しいコンポジットオブジェクトのために、アダプタを作成するような場合でも同様です。

*1 たとえば、コンポジットオブジェクトを交換可能にすることで、同じコンテナクラスから違う動作をするオブジェクトを生成することもできます。
*2 これらの機能は、アダプタ以外の目的にも使用できます。
*3 上述のように、インターフェースの活用とは、静的型付け言語にのみ関連するトピックです。