値オブジェクトの実装コード
最後に、値オブジェクトの実装について見ていきましょう。ここではアジャイルプロジェクト管理コンテキストで、ビジネス的な価値を計算する「ビジネス優先度(BusinessPriority)」に関する実装(Java/C#)を見ていきます。
![BusinessPriorityに関する値オブジェクト群](http://cz-cdn.shoeisha.jp/static/images/article/10184/10184_04_s.gif)
値オブジェクトである「ビジネス優先度」は、エンティティ「バックログアイテム」の属性の一部です。「ビジネス優先度」はビジネス優先度レーティングの属性を保持し、ビジネス優先度合計を使用して各種計算を行います。
BusinessPriorityTotalsクラス(値オブジェクト)の実装
BusinessPriorityクラスのメソッドで使用される「BusinessPriorityTotals」の実装から見ていきましょう。これはビジネス優先度の合計値を格納する値オブジェクトで、全てのバックログアイテムに関するコストやリスクの総計を格納します。BusinessPriorityクラスにて、他のバックログアイテムと比較したり、パーセンテージを計算したりするために使用されます。
![BusinessPriorityTotals「値オブジェクト」の実装例(C#)](http://cz-cdn.shoeisha.jp/static/images/article/10184/10184_05_s.gif)
これまで紹介してきた通り、コンストラクタで全ての属性値を設定でき、外部から変更できるSetterが存在しないことがわかります。これにより、値オブジェクトの条件である「不変性」を満たしています。
また、値オブジェクトのインスタンスが等しいかどうかを判断する「等価性」の判定を属性値が全て同じかどうかで判断するため、GetEqualityComponentsメソッドをオーバーライドして、全ての属性の値を返しています。親クラスであるValueObjectにてEqualsメソッド等の共通処理が実装されているので、これによって等価判定が可能となります。
BusinessPriorityRatingsクラス(値オブジェクト)の実装
次に「BusinessPriorityTotals」の実装を見てみましょう。このクラスはビジネス優先度の合計値を格納する値オブジェクトです。特定のプロダクトバックログアイテムを実装する際に、どの程度の事業価値と出費があるかを表現しています。
![BusinessPriorityRatings「値オブジェクト」の実装例(C#)](http://cz-cdn.shoeisha.jp/static/images/article/10184/10184_06_s.gif)
先ほどの「BusinessPriorityTotals」クラスとの違いはコンストラクタにバリデーションチェックが入っていることでしょう。各引数(ベネフィット、ペナルティ、コスト、リスク)の値が1〜9以外であればエラーとなるようにアサーションを宣言しています。バリデーションについては、第5回のエンティティの記事にて紹介しているので、詳細はそちらをご覧ください。
属性を変更した値を戻すWith〜メソッド
値オブジェクトは不変であることを前提としていますが、交換可能性の部分で紹介したように、特定の属性の値を変更した新しいオブジェクトを生成することが可能です。このクラスではWithから始まるメソッドで既存の値をコピーした新しいインスタンスを生成しています。例えば「WithAdjustedCost(int cost)」メソッドの場合、引数で受け取ったコストの値のみ変更しています。既存の値を使用してコンストラクタを呼び出すことで、一部の値のみ変更したオブジェクトを生成しています。
BusinessPriorityクラス(値オブジェクト)の実装
最後に、BusinessPriorityクラスを見てみましょう。このクラスではビジネスプライオリティのビジネスロジックを計算しています。
![BusinessPriority「値オブジェクト」の実装例(C#)](http://cz-cdn.shoeisha.jp/static/images/article/10184/10184_07_s.gif)
このクラスでは、ビジネスロジックに応じた計算を行っています。各メソッドでは、自分自身の属性と引数の値を使って計算していることがわかります。Setterはなく、不変で、副作用がないこともわかると思います。
プライマリコンストラクタとコピーコンストラクタ
なお、BusinessPriorityクラスには2種類のコンストラクタが存在しています。一つは「プライマリコンストラクタ」で、これまで登場している普通のコンストラクタです。「public BusinessPriority(BusinessPriorityRatings ratings)」の部分では、ビジネスプライオリティレーティングを引数として受け取り、新しいオブジェクトを生成しています。
これに対して「public BusinessPriority(BusinessPriority businessPriority)」が「コピーコンストラクタ」となります。このコンストラクタでは自分自身を引数として受け取ります。このようなコピーコンストラクタは、値オブジェクトに不変性がないテストを書くときによく使用されます。なお、値オブジェクトのテストでは、ドメインエキスパートとビジネス的な話をして、その仕様を満たすようにテストコードを記載するように努めます。
最後に
以上、本記事ではDDDの値オブジェクトについて紹介しました。値オブジェクトの概要を理解し、エンティティや標準型との違いや実装方法について学べたかと思います。DDDでは「優先度」や「電話番号」といったユビキタス言語をプリミティブな数値型で済ませてしまうのではなく「値オブジェクト」として表現することを理解できたと思います。次の第7回ではDDDの「サービス」について紹介します。
参考資料
- 可変性か、不変性か? (IBM developerWorks)
- immutable (++C++ 未確認飛行 C ブログ)
- リファクタリングメモ - タイプコードの置き換え
- アナリシスパターン:多国通貨 (オブジェクトの広場)
- CHECKS 情報の一貫性に関するパターン言語 (Ward Cunningham)
- オブジェクト指向と関数型で副作用の扱いが違うって知ってた?(タニマチェンコさん)
- 区分値(dbflute)
- オブジェクト指向言語における列挙型の意義(パワータイプの紹介、吉田誠一さん)
- C#のEnumを(Javaのように)別の値を持たせるなど拡張する(neueさん)