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

UMLの関連を分かりやすく実装するクラス(C#)

More than 1 year has passed since last update.


はじめに

以前の記事

クラス図の書きかた(集約とコンポジションの意味の違い)

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

では、集約とコンポジションのC#実装例を書きました。

しかし、コンポジションの実装パターンを、毎回コーディングするのは面倒だと思います。ですので、集約やコンポジションを設定できる「関連」を実装しやすくするクラスを作りました。


コードはこちら

https://github.com/azuki8/umlLib

この中の[umlLib]-Association.csが関連クラスです。

※これ以外は基本的に単体テスト用コードなので不要。


「関連」クラスを使った実装イメージ

サンプルコード中の「Association<T>」が「関連」クラスです。

<T>に関連端の型を入れて使います。

サンプルコードでは、関連端の型は「Hogera」です。


Sample.cs

class Sample {

// 関連
private Association<Hogera> m_hogera;

// コンストラクタ
public Sample(){
// Hogera型への関連
// 引数:
// コンポジット、多重度[0..5]、初期値なし、unique制約なし、ordered制約あり、orderedの順序を決めるプロパティ="Name"
m_hogera = new Association<Hogera>(AgreeKindEnum.Composite, 0, 5, null, false, true, "Name");
}
// 何らかの処理関数
public void SomeFunction(String hogeraName)
{
// 関連に要素をAddする
bool ans = m_hogera.Add(new Hogera(hogeraName));
// 関連端へのアクセス
foreach(Hogera hogera in m_hogera.Ends)
{
// 関連で多重度がある扱いということは、取り立てて区別するものではないとの認識なので、foreachでのみアクセス可能
hogera.Func();
}
}


こんな感じで、Association<T>で関連を持つことができます。

第1引数でコンポジット(=コンポジション)を選ぶと、

関連端の要素が、複数の全体概念に保持されないようにチェックされます。

第2,第3引数で多重度を設定します。

多重度は、初期化・Add・Remove時にチェックされます。

その他、初期値や unique , ordered制約 , orderedの順序を決めるプロパティ名を指定できます。


「関連」クラスの説明


概要

UMLでいうところの「関連」をイメージしたクラスです。

ただし、UML仕様書の「関連」をそのままコードにしたものではありません。

従来の関連の実装のしかたに、集約や多重度などの機能を追加したラッパーのようなものだとご理解ください。

「関連」の実装しやすさやコードの読みやすさを優先させて考えました。


クラスの種類

関連端の型<T>を指定するクラスを含め、以下のクラスを用意しています。


  • 関連端の型<T>を指定するクラス

  • 限定子<TKey>と関連端の型<TValue>を指定するクラス

  • 限定子<TKey1,TKey2>と関連端の型<TValue>を指定するクラス

  • 多重度が固定値になる FixedAssociationクラス


全クラスの公開プロパティ/公開メソッド


Association<T>クラス

関連 多重度[n..m]


Association.cs

/// <summary>

/// 関連 多重度[n..m]
/// </summary>
/// <typeparam name="T">関連端の型</typeparam>
public class Association<T>

/// <summary>
/// 関連端(関連の要素群)
/// </summary>
/// <remarks>関連の要素リストを取得できます</remarks>
public IEnumerable<T> Ends

/// <summary>
/// 関連端(関連の要素)
/// </summary>
/// <remarks>多重度1に設定された関連で利用するプロパティ(多重度1までの関連ではイテレータを使わずにすむようにするため)。多重度2以上ではFailします。/remarks>
public T End

/// <summary>
/// 関連端の要素数
/// </summary>
public int Count

/// <summary>
/// 関連 0..m のコンストラクタ
/// </summary>
/// <param name="_agreeKind">集約種別</param>
/// <param name="_maxSize">多重度 最大値m</param>
/// <param name="_defaultList">m個以内の初期要素リスト</param>
/// <param name="_isUnique">{unique}制約 true(あり) false(なし)</param>
/// <param name="_isOrdered">{orderd}制約 true(あり) false(なし)</param>
/// <param name="_orderPropertyName">orderedの順序を決めるプロパティ名</param>
public Association(AgreeKindEnum _agreeKind, int _maxSize, List<T> _defaultList = null, bool _isUnique = true, bool _isOrdered = false, string _orderPropertyName = "")

/// <summary>
/// 関連 n..m のコンストラクタ
/// </summary>
/// <param name="_agreeKind">集約種別</param>
/// <param name="_minSize">多重度 最小値n</param>
/// <param name="_maxSize">多重度 最大値m</param>
/// <param name="_defaultList">n~m個の初期要素リスト</param>
/// <param name="_isUnique">{unique}制約 true(あり) false(なし)</param>
/// <param name="_isOrdered">{orderd}制約 true(あり) false(なし)</param>
/// <param name="_orderPropertyName">orderedの順序を決めるプロパティ名</param>
public Association(AgreeKindEnum _agreeKind, int _minSize, int _maxSize, List<T> _defaultList = null, bool _isUnique = true, bool _ordered = false, string _orderPropertyName = "") : this(_agreeKind, _maxSize, _defaultList, _isUnique, _ordered, _orderPropertyName)

/// <summary>
/// 関連 0..1 のコンストラクタ
/// </summary>
/// <remarks>初期要素0のコンストラクタ</remarks>
/// <param name="_agreeKind">集約種別</param>
public Association(AgreeKindEnum _agreeKind) : this(_agreeKind, 0, 1, null, false, false, "")

/// <summary>
/// 関連 0..1 のコンストラクタ
/// </summary>
/// <remarks>初期要素のあるコンストラクタ</remarks>
/// <param name="_agreeKind">集約種別</param>
/// <param name="_defaultValue">初期要素</param>
public Association(AgreeKindEnum _agreeKind, T _defaultValue) : this(_agreeKind)

/// <summary>
/// 指定要素を加える
/// </summary>
/// <param name="value">追加対象の要素</param>
/// <returns>true:成功、false:失敗</returns>
public bool Add(T value)

/// <summary>
/// 指定要素を取り除く
/// </summary>
/// <param name="value">除外対象の要素</param>
/// <returns>true:成功、false:失敗</returns>
public bool Remove(T value)

/// <summary>
/// 要素の差し替え
/// </summary>
/// <param name="_old">差替前の要素</param>
/// <param name="_new">差替後の要素</param>
/// <remarks>コンポジションで他の全体に保持されている要素に差し替えようとすると失敗します</remarks>
/// <returns>true:成功、false:失敗</returns>
public bool Change(T _old, T _new)
}



Association<TKey, TValue>クラス

関連 限定子付き 多重度[0..1]


Association.cs

/// <summary>

/// 関連 限定子付き 多重度[0..1]
/// </summary>
/// <remarks>限定子1つ。限定子に対して多重度[0..1]</remarks>
/// <typeparam name="TKey">関連端の限定子の型</typeparam>
/// <typeparam name="TValue">関連端の型</typeparam>
public class Association<TKey, TValue>
{
/// <summary>
/// 全ての関連端
/// </summary>
public IEnumerable<KeyValuePair<TKey, TValue>> AllEnd

/// <summary>
/// 指定された限定子の関連端
/// </summary>
/// <param name="key">限定子の値</param>
/// <returns>指定された限定子の関連端</returns>
public TValue End(TKey key)

/// <summary>
/// 指定された限定子の関連端の要素数
/// </summary>
/// <param name="key">限定子の値</param>
/// <returns>指定された限定子の関連端の要素数</returns>
public int Count(TKey key)

/// <summary>
/// 関連 限定子付き 0..1 のコンストラクタ
/// </summary>
/// <param name="_agreeKind">集約種別</param>
public Association(AgreeKindEnum _agreeKind)

/// <summary>
/// 関連 限定子付き 0..1 のコンストラクタ
/// </summary>
/// <param name="_agreeKind">集約種別</param>
/// <param name="_key">初期要素の限定子の値</param>
/// <param name="_value">初期要素</param>
public Association(AgreeKindEnum _agreeKind, TKey _key, TValue _value) : this(_agreeKind)

/// <summary>
/// 指定要素を加える
/// </summary>
/// <param name="key">追加対象の限定子の値</param>
/// <param name="value">追加要素</param>
/// <returns>true:成功、false:失敗</returns>
public bool Add(TKey key, TValue value)

/// <summary>
/// 指定要素を取り除く
/// </summary>
/// <param name="key">除外対象の限定子の値</param>
/// <returns>true:成功、false:失敗</returns>
public bool Remove(TKey key)

/// <summary>
/// 変更
/// </summary>
/// <param name="_key">差替対象の限定子</param>
/// <param name="_new">差替後の要素</param>
/// <returns>true:成功、false:失敗</returns>
public bool Change(TKey _key, TValue _new)
}



Association<TKey1, TKey2, TValue>クラス

関連 限定子付き 多重度[0..1]


Association.cs

/// <summary>

/// 関連 限定子付き 多重度[0..1]
/// </summary>
/// <remarks>限定子2つ。2つの限定子の組合せに対して多重度[0..1]</remarks>
/// <typeparam name="TKey1">関連端の限定子1の型</typeparam>
/// <typeparam name="TKey2">関連端の限定子2の型</typeparam>
/// <typeparam name="TValue">関連端の型</typeparam>
public class Association<TKey1, TKey2, TValue> where TValue : class // where T : class が Tは参照型のみの意味
{
/// <summary>
/// 関連 2限定子付き 0..1 のコンストラクタ
/// </summary>
/// <param name="_agreeKind">集約種別</param>
public Association(AgreeKindEnum _agreeKind)

/// <summary>
/// 関連 限定子付き 0..1 のコンストラクタ
/// </summary>
/// <param name="_agreeKind">集約種別</param>
/// <param name="_key">初期要素の限定子の値</param>
/// <typeparam name="TKey1">初期要素の限定子1の値</typeparam>
/// <typeparam name="TKey2">初期要素の限定子2の値</typeparam>
/// <param name="_value">初期要素</param>
public Association(AgreeKindEnum _agreeKind, TKey1 _key1, TKey2 _key2, TValue _value) : this(_agreeKind)

/// <summary>
/// 全ての関連端
/// </summary>
public IEnumerable<KeyValuePair<Tuple<TKey1, TKey2>, TValue>> AllEnd

/// <summary>
/// 指定された限定子の関連端
/// </summary>
/// <typeparam name="TKey1">限定子1の値</typeparam>
/// <typeparam name="TKey2">限定子2の値</typeparam>
/// <returns>指定された限定子の関連端</returns>
public TValue End(TKey1 _key1,TKey2 _key2)

/// <summary>
/// 指定された限定子の関連端の要素数
/// </summary>
/// <typeparam name="TKey1">限定子1の値</typeparam>
/// <typeparam name="TKey2">限定子2の値</typeparam>
/// <returns>指定された限定子の関連端の要素数</returns>
public int Count(TKey1 _key1, TKey2 _key2)

/// <summary>
/// 指定要素を加える
/// </summary>
/// <param name="key1">追加対象の限定子1の値</param>
/// <param name="key2">追加対象の限定子2の値</param>
/// <param name="value">追加要素</param>
/// <returns>true:成功、false:失敗</returns>
public bool Add(TKey1 key1, TKey2 key2, TValue value)

/// <summary>
/// 指定要素を取り除く
/// </summary>
/// <param name="key1">除外対象の限定子1の値</param>
/// <param name="key2">除外対象の限定子2の値</param>
/// <returns>true:成功、false:失敗</returns>
public bool Remove(TKey1 key1, TKey2 key2)

/// <summary>
/// 変更
/// </summary>
/// <param name="key1">差替対象の限定子1の値</param>
/// <param name="key2">差替対象の限定子2の値</param>
/// <param name="_new">差替後の要素</param>
/// <returns>true:成功、false:失敗</returns>
public bool Change(TKey1 key1, TKey2 key2, TValue _new)
}



FixedAssociation<T>クラス

関連 多重度[n]


Association.cs

/// <summary>

/// 関連 多重度[n]
/// </summary>
/// <typeparam name="T">関連端の型</typeparam>
public class FixedAssociation<T>
{
/// <summary>
/// 関連 多重度n のコンストラクタ
/// </summary>
/// <param name="_agreeKind">集約種別</param>
/// <param name="_no">多重度[n]</param>
/// <param name="_defaultValue">n個の初期要素リスト</param>
/// <param name="_isUnique">{unique}制約 true(あり) false(なし)</param>
/// <param name="_isOrdered">{orderd}制約 true(あり) false(なし)</param>
/// <param name="_orderPropertyName">orderedの順序を決めるプロパティ名</param>
public FixedAssociation(AgreeKindEnum _agreeKind, int _no, List<T> _defaultValue, bool _isUnique = true, bool _isOrdered = false, string _orderPropertyName = "")

/// <summary>
/// 関連 多重度1 のコンストラクタ
/// </summary>
/// <param name="_agreeKind">集約種別</param>
/// <param name="value">初期要素</param>
public FixedAssociation(AgreeKindEnum _agreeKind, T value) : this(_agreeKind,1, new List<T>() { value }, false,false,"")

/// <summary>
/// 関連端(関連の要素群)
/// </summary>
/// <remarks>関連の要素リストを取得できます</remarks>
public IEnumerable<T> Ends

/// <summary>
/// 関連端(関連の要素)
/// </summary>
/// <remarks>多重度1に設定された関連で利用するプロパティ(多重度1までの関連ではイテレータを使わずにすむようにするため)。多重度2以上ではFailします。/remarks>
public T End

/// <summary>
/// 関連端の要素数
/// </summary>
public int Count

/// <summary>
/// 変更
/// </summary>
/// <param name="_old">差替対象の要素</param>
/// <param name="_new">差替後の要素</param>
/// <returns>true:成功、false:失敗</returns>
public bool Change(T _old, T _new)
}



AgreeKindEnum列挙体

集約種別 None:普通の関連、Shared:集約(白抜きひし形の関連)、Composite:コンポジション(黒塗りひし形の関連)


Association.cs

/// <summary>

/// 集約種別 None:普通の関連、Shared:集約(白抜きひし形の関連)、Composite:コンポジション(黒塗りひし形の関連)
/// </summary>
public enum AgreeKindEnum
{
None,
Shared,
Composite,
}


(補足1)なぜ、関連のクラスを実装したのか

UMLモデリングツールでコード生成すると、属性と関連は同じコードとして出力されることがありますが、

オブジェクト指向で分析モデリングするとき、

「オブジェクト間のつながり」と「オブジェクトの属性(=特徴や性質)」は、私の中では異なるものです。

これらをコーディング時に同じ実装にすると、コードを読んだ時の直感的な違いがなくなってしまいます。

モデルベース開発は、モデルとコードとをツールで相互変換しながら開発することも可能ですので、「モデルに戻るからいいじゃないか」とも思えますが、詳細を作りこむ/不具合調査する段階は、手作業が多いと思います。

その時は、コードを見ながら実装するわけですから、直感的な違いがなくなると、モデル上での認識が歪みやすくなります。

コードに手を入れる人の全員がモデルを意識できるほどの実力のある開発現場ばかりとは言えないですから、プログラマにやさしい実装方法を考えた方が良いと思うのです。

例えば、「社員クラス」の属性に「氏名クラス」が付いているとき、「属性ならば社員インスタンスの特徴(一部)なんだな、」という印象を受けると思います。

それに対して、「社員クラス」の関連に「氏名クラス」が付いているとき、「社員インスタンスとつながりを持った別のもの(例えば工作員としての名前とか^^;)」と認識されやすくなります。

モデルの段階でどちらと認識されて作られたものなのかを直感的に読み取れずに作業すると、インスタンスの特徴として認識したり、社員インスタンスとつながりを持った別のものだと認識したりするようになり、開発効率や不具合混入に大きく影響すると考えます。

モデルベースで開発するならば、そこまで考えてお膳立てすると、私個人は分かりやすいと思うのです。


(補足2)UMLに詳しい方向けの注意

関連クラスではありません。

UML仕様の関連をそのまま実装したものではありません。


おわりに

このクラスは、githubに上げてあります。

このようなUMLを、プログラマとして分かりやすい形式で実装できる補助ライブラリを作って、

分析モデルからm2tで、補助ライブラリを利用したコードを生成することで、モデルベース/モデル駆動開発でありながら、

プログラマがコードを読みやすくメンテナンスしやすい開発環境を作ってみたいと思います。

まだ自分でツール開発などで実践に使って改善すると思いますので、時々githubの更新を覗いてください。

よろしくお願いします。