値オブジェクトの特徴(2)
(3)概念的な統一体
値オブジェクトは複数の属性(プロパティ)を保持しており、それらが相互に関係して値を説明することができます。例えば「通貨」という値オブジェクトの場合、円やドルといった「単位」と、その数量を表す「金額」を組み合わせることによって、適切な値を示すことができます。
一つの属性値だけでは意味を持たず、それぞれが組み合わさることで適切な説明をできることを「概念的な統一体」と呼びます。通貨の場合、「100」や「円」だけでは意味を持ちませんが、「100円」であれば、完結した意味を持つ値となります。
(4)交換可能性
値オブジェクトは(2)不変性の特徴を持つため、途中で値の変更を行うことができません。しかし、実際のプログラムでは値の変更を行いたいことがあると思います。そのようなときは、変更後の値を設定した新しいオブジェクトを生成して交換します。このように交換できることを「交換可能性」と呼びます。通貨の金額を変更したい場合、現在の値オブジェクトの値を元に新しい通貨オブジェクトを生成し交換します。
なお、この手順が不便と感じて、エンティティを採用することがないように注意してください。前章でご紹介した通り、長期にわたって識別する必要があるオブジェクトだけがエンティティとなります。
(5)等価性
値オブジェクト同士のインスタンスを比較する場合、等しいかどうかを判断する「等価性」の判定方法を提供する必要があります。エンティティでは「一意な識別子が同じか」で判定しましたが、値オブジェクトでは「各属性が持つすべての値が同じか」で判定します。
つまり、属性の識別だけで同じかどうかを判断できる場合は、エンティティではなく値オブジェクトを使えないか検討してみてください。
(6)副作用のない振る舞い
「副作用のない関数」とはエヴァンス氏がDDD本の「しなやかな設計」の部分で紹介しているパターンで、値オブジェクトの条件の一つです。副作用のない操作とは、「どのような状態で何回呼び出してもオブジェクトの状態が変わらない」操作を意味します。
「副作用がない関数」とわかっていれば、そのメソッドを呼び出すときに、他への影響がないため、安心して実装やテストを書けます。4章で紹介したCQRSパターンでは、変更するコマンド(Command)と取得するクエリ(Query)を明確に分離していましたが、このクエリ部分が副作用のない関数となります。
値オブジェクトは(2)の「不変」を満たすため、値を変更するメソッドは存在しません。そのため、副作用はなさそうに思えます。しかし情報を取得するクエリでも、注意していないと副作用のある操作になる場合があります。例えば、三角形の面積を計算(縦と横を掛け2で割る)する関数で考えてみましょう。
public int GetAreaSize(TriangleEntity entity) { entity.AreaSize = entity.Height * entity.Width / 2; return entity.AreaSize; }
メソッド名もGet~となっており、一見、副作用はなさそうに見えます。しかし、このメソッドでは引数にエンティティを渡しており、そのAreaSizeプロパティの値を更新しています。そのため、このメソッドは「副作用がある」メソッドといえます。
加えて、値オブジェクトでは可能な限り自分自身の情報だけを使って処理を行うことが望ましいとされています。このメソッドでは、引数のエンティティの情報を使って処理を行っています。これではエンティティの内部構造も理解する必要があるため複雑です。
これらを改善した副作用のないコードを見てみましょう。
public int GetAreaSize(int height, int width) { int result = height * width / 2; return result; }
ここでは、引数としてエンティティを渡すことをやめています。エンティティのAreaSizeプロパティの値を変えなければ副作用がないともいえますが、呼び出し側に不安を感じさせることや、将来的に不変でなくなるリスクが高いこともあるため、エンティティのプロパティの値を渡すように変更しています。これで副作用のないメソッドとなりました。
以上、値オブジェクトの特徴について紹介しました。