C#
オブジェクト指向
uml
モデリング
クラス図

【コードで分かるUMLシリーズ】コンポジションのサンプルコード(「クラス図の書きかた(集約とコンポジションの意味の違い)」の補足)

この記事について

この記事は、クラス図の書きかた(集約とコンポジションの意味の違い)
のサンプルコードの補足です。

クラス図の書きかた(集約とコンポジションの意味の違い)に説明した部分は触れませんので、まずクラス図の書きかた(集約とコンポジションの意味の違い)のサンプルコードの説明を読んでから、続けて読んでください。

前回の振り返り

少しだけ振り返ると、

集約 = 同一の部分インスタンスが、複数の全体インスタンスに属することができる。
コンポジション = 同一の部分インスタンスは、複数の全体インスタンスに属することができない。
の違いがありました。

サンプルコード

前回のサンプルコードでは、この属することができないの部分を実装していなかったので、
今回のサンプルコードでは、その部分を#if true と #if falseで、追加しました。

コード全体

Hoge.cs
class Hoge : IDisposable
{
    // コンポジション
    private List<Piyo> m_piyos = new List<Piyo>();
#if true
    // 部分が他の全体に属しているかをチェックするリスト
    private static List<Piyo> m_piyosCheckList = new List<Piyo>();
    private static object m_checkListLock = new object();
#endif
    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);
    }
#if false
    public void Addpiyo(Piyo piyo)
    {
        m_piyos.Add(piyo);
    }
#else
    public bool Addpiyo(Piyo piyo)
    {
        lock (m_checkListLock)
        {
            // この要素がすでに全体に属しているかチェック
            if (m_piyosCheckList.Contains(piyo) == true)
            {
                // すでに他の全体に属しています
                return false;
            }
            // 部分として保持
            m_piyos.Add(piyo);
            // この要素が全体に属したことを記憶する
            m_piyosCheckList.Add(piyo);
            return true;
        }
    }
#endif
#if false
    public bool Removepiyo(Piyo piyo)
    {
        return m_piyos.Remove(piyo);
    }
#else
    public bool Removepiyo(Piyo piyo)
    {
        lock (m_checkListLock)
        {
            // 部分としてのリストから削除
            bool ans = m_piyos.Remove(piyo);
            if (ans == true)
            {
                // この要素が全体に属したことを記憶から消す
                m_piyosCheckList.Remove(piyo);
            }
            return ans;
        }
    }
#endif
}

チェックリストの説明

一番上の

    // 部分が他の全体に属しているかをチェックするリスト
    static List<Piyo> m_piyosCheckList = new List<Piyo>();
    private static object m_checkListLock = new object();

は、部分要素を、誰かが保持しているかのリストです。
static なので、どのHogeインスタンスからも参照されます。
このリストを見ることで他のHogeインスタンスが保持していないかをチェックしています。
Lockは、m_piyosCheckListの排他処理のためのものです。

AddとRemoveの処理は、lock (m_checkListLock){ ... } で囲って、排他しています。

Addの説明

あとは、HogeのAdd()の最初に、

        // この要素がすでに全体に属しているかチェック
        if (m_piyosCheckList.Contains(piyo) == true)
        {
            // すでに他の全体に属しています
            return false;
        }

で、実際に部分として保持する前にチェックし、その後、下記のようにチェックリストに加えます。

        // この要素が全体に属したことを記憶する
        m_piyosCheckList.Add(piyo);

Removeの説明

また、Removeできたときは、

                // この要素が全体に属したことを記憶から消す
                m_piyosCheckList.Remove(piyo);

でチェックリストからもRemoveします。

サンプルコード利用上の注意

サンプルコードを利用する際は、各自の責任のもとご利用ください。
複数スレッドからの動作確認テストは行っていません。
もし、不具合と思われる点が見つかりましたら、この記事にコメントでフィードバックをお願いします。

最後に

今回のサンプルコードでは、「全体」がHogeクラスしかない前提で作成しました。
ですので、もし、「全体」がHogeクラスのほかにHogeraクラスがあったとすると、
「部分」を共有されてしまう可能性があります。

そもそも、UML仕様書ではどう定義しているのかと思いみてみると、

Composite aggregation is a strong form of aggregation that requires a part instance be included in at most
one composite at a time.
合成集約は、部分インスタンスが一時店では最大でも1つの合成物にしか含まれてはならないとする、集約の強い形態である。(OMG 西原 2006)

とあります。

厳密に、上述のHogeとHogeraのようなケースについては言及を見つけられなかったので、
compositeの英語的な意味
Oxford英英辞典 「composite : made of different parts or materials」
から推測すると、HogeとHogeraであっても共有してはダメなのだろうなと思います。

ですので、複数のクラスが同一の「部分」クラスを使うような場合を想定する場合のために、
次のようなクラスを作ると、対応させることができました。

CompositPartList.cs
    /// <summary>
    /// コンポジションで全体に含まれている部分要素のリスト
    /// </summary>
    class CompositPartList
    {
        /// <summary>
        /// 集約種別がコンポジションのとき、コンポジションの部分要素が他の全体要素に属しているかをチェックするためのリスト
        /// </summary>
        public static List<object> compsitList = new List<object>();
        /// <summary>
        /// コンポジションのチェックリストの排他用ロック
        /// </summary>
        public static object compsitLock = new object();
    }

ですが、さすがに実装が面倒になってきたので、集約やコンポジション自体をクラスライブラリにしてしまうほうが良さそうです。
乞うご期待。

追記(2017/11/26)

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