結論
「XはAする」や「XはBする」を「XはそのXaでAする」「XはそのXbでBする」と書いても同じ意味になるとき、XはXaやXb等からなるコンポジションである
例:「人は走る」を「人はその足で走る」と書いても同じ意味なので人は足等からなるコンポジションである
歴史的な起源
コンポジション(合成集約)はその起源をアリストテレスの「合成」に持ちます。合成と言うとAとBを合成したらXになる、という意味にとらえがちですが、その起源は逆で、仮にXをXaとXbに分解した場合、XaとXbはそれぞれXの一部である。ではXは何と呼ぶべきか?->XはXaとXbの合成物と呼ぼうという話です。なので主題は合成できることというよりは「分解しても大意が変わらない」事です。
プログラミング上でのコンポジション
よく「再利用のためにはコンポジションを使おう」と言いますが、これも順序が逆です。正しくは「正しく分解したものなら別の文脈でも使えるかもしれない」というもので、主題はオブジェクトを正しく分解することです。
正しく分解する例
人ができることを全て「人は〇〇する」と書くと人オブジェクトにあらゆるメソッドが集中してしまいます。
そこで、「例えばここでは」手と足を分解します。
つまり、
人はその「足」で走る
人はその「足」でボールを蹴る
人はその「腕」でダンベルを持ち上げる
のように分け、分割したオブジェクトに記述を移行します。これは広い意味での「委譲」です。このとき、「人はその行為を委譲できるオブジェクトからなるコンポジションである」という事になります。
この分解は設計者が任意に、つまり「こう分解したらわかりやすいかな」と判断して分けます。例えば頭が登場しないなら頭に分解する必要は有りませんし、「人はその頭で考える」メソッドが必要になってから頭を分けても良いです。
合成(分解)のメリット
部品は委譲された行為にのみ責任を持つ
人が「足」を怪我するなどにより「足」の機能を失った場合、「足」を使った行為、例えば「走る」「ボールを蹴る」ができなくなりますが、「腕」を使った行為には支障はないでしょう。つまり、走れるか?などの判断を
人.isRunnable()
人.isKickable()
ではなく、
人.足.isRunnable()
人.足.isKickable()
と書くことで「それは足の問題だ」「腕は関係が無い」ことを明示することができます。人にやたらとメソッドが集まることも防げますし、足オブジェクトも自分の状態だけを見ればよいという事になります。
分解できるという事は、詳細に記述できるという事
単純に人は走るよりも人はその「足」で走る。の方が詳細です。
人は色を塗る、よりも人はその手に持つ絵筆で色を塗る、と書くほうが詳細です。
この時、人は絵を描きその工程の一部として絵筆で色を塗る、と記述することもできます。この場合は人の行為をまとめて記述する一方、委譲先である絵筆ではより細かい行為に言及することができます。
また、「実際に色を塗るのと色は絵筆の問題であり人はそれを選択するだけ」「動かし方は持つ手の問題である」という風に責務を分けることができます。
委譲のルールを作ることができる
いや「俺は腕で走れる」とか言い出す人がいるかもしれません。
いやそうかもしれないけど「今ここでは足を使って走ることに限定しよう」というルールを作ることができます。
ルールに従った行為は、同じくルールに従った他のオブジェクトでもきっと同じ行為でしょう。同じ行為であるということは再利用ができるという事です。
部品は交換できる(こともある)
人はその手に持つエアブラシで色を塗ることもできます。このように、合成する部品は交換可能なことがあります。
集合やコンテナはコンポジションではない
集合にも全体と部分はありますし、コンテナに何かを入れた場合はコンテナは全体っぽくなります。
が、集合やコンテナは中身を使って何かをしたりしません。よって集合やコンテナはコンポジションではありません。集合かコンポジションか迷ったら、全体がその中身によって異なる挙動をするか考えましょう。
モデリングにおけるコンポジション
モデルを作る際にコンポジションを重視する流派があります。(ドメインドリブンなど)。これは「基本的にはまとまったオブジェクトを見るけど必要に応じて細部をモデル化していこう」という考えによります。そして、分割したオブジェクト―例えば「腕」や「足」は全体である「人」が管理します。実を言うと必ずそうしなければならない、というわけではありません。単にそのほうが整合性がとりやすいからです。
部品を全体の外に出してはいけない、出すならばそのコピーや値としてのオブジェクトを、というのも同様に単に「部品が全体の外にあるのは論理的に間違っている」からです。
良くある「ほぼ正確だが微妙な記述」
一つの部品を同時に複数のオブジェクトにコンポジションすることはできない
これは部品の管理者が複数いてはいけない、という話なので実際には「管理者が必要なければできる」という話になります。具体的には完全に不変なオブジェクトは管理する必要が無いので複数のオブジェクトに同時にコンポジションすることができます。状態を持つ普通のオブジェクトではやめておきましょう。
コンポジションした場合、全体と部分のライフサイクルは同じである
これは「ほとんどの場合は全体が部分を管理するのでそうなる」という話で上記と同じく管理する必要が無い、または管理者が決まっていれば別の話です。例えばDIであるオブジェクトに部品を注入した場合、そのオブジェクトはコンポジションですが部品のライフサイクルはDIコンテナが握ります。借り物の部品でもコンポジションにはなる、という事です。
再利用には継承よりもコンポジションを使うべき
そもそも継承は「それを継承したオブジェクトは同じものとして扱える」ことを保証するための物であり実装の継承は「同じものとして扱えるから差分以外は同じコードである」ことを保証するものです。またコンポジションは「合成したものは部品に分解できる」ことを保証するものなのでどちらも再利用のためのものではありません。ただし、部品のほうが再利用しやすいのは確かでしょう。
できる限りコンポジションを使おう
部品が単純かつ不変な値オブジェクト、たとえば文字列とか数とか色ならばそれは「属性」にまとめたほうがすっきりします。少なくともクラス図にクラスを大量に書く必要はなくなります。ではそれは属性か?というのは大変難しい問題ですが。一つの基準としては、プリミティブ型や「一言で特定のオブジェクトに言及できて重要な機能を持たないもの」は属性としても良いでしょう。例えば色はプリミティブ型ではないでしょうが、「赤」や#FF0000と言って特定の色にアクセスできるならそれは属性でしょう。ただし重要な機能や相互作用を持つ場合は属性っぽくてもコンポジションとしたほうが良いでしょう。設計とはオブジェクト同士の相互作用を記述するものですから。
追記
実際のUMLではどうなのか?という話を忘れてました。もう10年近く前なのか…これまだ正しいのかな…
[http://labo.mamezou.com/special/sp_002/sp_002_001.html]
細部に違いが有るのはUMLは不変オブジェクトを重視してないことによるものですね。