追記(2017/11/26)

この記事で紹介している内容を使って、集約やコンポジションの実装を補助するクラスを作りました。
UMLの関連を分かりやすく実装するクラス(C#)をどうぞ。

集約とコンポジションの使い分け

クラス図で、「白抜きのひし形(集約)」と「黒塗りのひし形(コンポジション)」をどのように使い分けたらよいのか? 悩んだ経験はありませんか?

集約とコンポジションの違いを誤解している人がいるようですので、同じ誤解をしている人のために記事にします。

モデリングだけだと、ピンとこない人のために、サンプルコードも付けました。

UMLをぼんやり知っているOさんの答え

白抜きのひし形は「has-a」=「所有している」という意味。
黒塗りのひし形は「part-of」=「~の一部」という意味。
だから、「人は車を所有する」は、白いひし形で表現し、「タイヤは車の一部」は、黒いひし形で表現する。
wrong_agg.png

これは、「part-of」が「has-a」と呼ばれることがあるために起きている誤解なのかも知れません。

正しい意味

まず、「ひし形」自体が「全体と部分」を表現しています。
つまり、白抜きも黒塗りも、ともに「全体と部分」=「part-of」です。
same_agg.png

白抜きは、全体と部分としての結びつきが緩やかで、
黒塗りは、全体と部分としての結びつきが強い、
という違いです。

白抜きのひし形(集約)の正しい意味

白抜きのひし形(集約)は、UML1.3のときは「共有集約」と呼ばれていました。
これは、複数の「全体」インスタンスが、1つの「部分」インスタンスを共有する
という意味です。
これが、全体と部分としての結びつきが緩やか、ということです。
「集約」と呼ばれるようになってからも、この意味自体は変わっていません。

例えば、「図形」には「スタイル(※線の色や図形の塗りつぶし)」があり、これは全体と部分とみることができます。
1つの「スタイル」インスタンスを、複数の「図形」インスタンスが共有しています。

white_agg.png

黒塗りのひし形(コンポジション)の正しい意味

黒塗りのひし形(コンポジション)は、共有集約と違い、「部分」が複数の「全体」に共有されません。
つまり、唯一の「全体」インスタンスが、1つの「部分」インスタンスを所属させます。
また、「部分」は「全体」と生存期間を共にすると考えられています。
そのため、「全体」側を削除すると「部分」側も連鎖的に削除されます。
ただし、全体を削除する前に「部分」を取り外すことで、連鎖的な削除を免れることは可能です。

例えば、「車」と「タイヤ」は「全体と部分」の関係で、1つの「タイヤ」インスタンスは、1つの「車」インスタンスにしか属すことができません。
車を廃車にすると、タイヤも壊されてしまいます。
ただし、車からタイヤを外してから廃車にしてしまえば、タイヤは別の車に付け替えることもできます。

black_agg.png

誤解していた「所有」はどう描くのか?

いわゆる「所有」、
人(飼い主)は、犬を飼っている
社員は、社員証を持っている
は、ただの関連です。
「所有」だと ひし形のような何か特別な記号で描く、ということ自体が誤解です。

relation.png
relation_has.png

C#サンプルコード

最後に、集約とコンポジションのサンプルコードを書いてみます。
image.png

集約

Hoge(全体)がPiyo(部分)を複数含んでいます。
部分をprivate List<Piyo> m_piyosで保持して、AddとRomoveだけをHoge経由で公開しています。
わざわざprivateなのは、
・List自体を外部から変更されないようにする
・Hogeが知らないところでm_piyosの中身を変えられないようにする
ためです。

車とタイヤの例で考えてみると、m_piyosを外部から= new List<Piyo>();されたら、
タイヤのシャフトのあたりを、他の空間と交換されてしまうような感じですよね。

また、Hogeが知らないところでm_piyosの中身を変えるということは、
車が街中を走っている途中で、突然、工場からタイヤを交換することができてしまったりする感じですよね。

もし、こんな世界観でプログラミングしていたら、不具合だらけですよね。
だから、モデリングで概念上の世界観を可視化したら、その世界観をコーディングで崩さないように気を付けて(街中を走行中に工場からタイヤを交換出来ないような)コードで実装するのです。

なお、Hogeのpublicメソッド名がAddではなくAddpiyoなのは、複数の「部分」を扱うことがあるためです。

Hoge.cs
class Hoge
{
    // 集約
    private List<Piyo> m_piyos = new List<Piyo>();

    public Hoge()
    {
    }

    public void Addpiyo(Piyo piyo)
    {
        m_piyos.Add(piyo);
    }

    public bool Removepiyo(Piyo piyo)
    {
        return m_piyos.Remove(piyo);
    }
}
Piyo.cs
class Piyo
{
    // 特になし
}

コンポジション

基本的には集約と変わりませんが、全体側を削除したときに部分側を削除する点が異なります。
ただ、C#はガベージ コレクションを使うので、それに任せるならば、集約と同じ実装で良いと考えます。
一応、Disposeで削除できるだけ削除するサンプルを書いてみました。
Disposeの実装方法については、下記の記事を参考にさせて頂きました。

Hoge.cs
class Hoge : IDisposable
{
    // コンポジション
    private List<Piyo> m_piyos = new List<Piyo>();

    public Hoge()
    {
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            foreach(Piyo piyo in m_piyos)
            {
                piyo.Dispose();
            }
            m_piyos.Clear();
            m_piyos = null;
        }
    }

    ~Hoge()
    {
        Dispose(false);
    }

    public void Addpiyo(Piyo piyo)
    {
        m_piyos.Add(piyo);
    }

    public bool Removepiyo(Piyo piyo)
    {
        return m_piyos.Remove(piyo);
    }
}
Piyo.cs
class Piyo : IDisposable
{
    // Disposeなど、削除に必要なものを実装
}

以上のサンプルコードでは、「部分の削除」を入れましたが、
あと、もう一つのコンポジションの特徴である「複数の全体に共有されないこと」の実現を追記したサンプルコードは、
別の記事コンポジションのサンプルコード(「クラス図の書きかた(集約とコンポジションの意味の違い)」の補足) に記載しました。
そちらもご覧ください。

参考

『オブジェクト指向システム開発 分析・設計・プログラミングへの実践的アプローチ』
              本位田 真一・山城 明宏 著 日経BP出版センター
『UMLモデリングのエッセンス 第2版 標準オブジェクトモデリング言語入門』
   マーチン・ファウラー/ケンドール・スコット 著 羽生田 栄一 監訳 翔泳社

『C# によるプログラミング入門 [メモリとリソース管理] [雑記] Dispose にまつわる余談』