値オブジェクト(Value Object)の実装

エリック・エバンズのドメイン駆動設計によると、オブジェクトはエンティティ(参照オブジェクト)値オブジェクトに分類できます。エンティティは一意性・継続性が問われるオブジェクト、それに対して値オブジェクトは一意性は問われず属性のみが注目されるオブジェクトです。

当方のドメイン層の設計では、データベースのテーブルに相当するオブジェクトはエンティティ、テーブル内のカラムの1つや複数のカラムを塊として扱うときは値オブジェクトとして表われます。

カラムは通常文字列・数値・日付など組み込み型で表されます。これらは値オブジェクトです。あえて新たな型を作成したい場合として、特殊なバリデーションを施したいときがあります。例えば電話番号に数値とハイフン以外を許可しないといった場合、そのようなバリデーションを組み込んだ電話番号型を作っておけば再利用できます(実際の設計とは異なります)。これも値オブジェクトです。

得意先マスタ
カラム名 プロパティ名 型名
得意先コード 得意先コード 整数
得意先名 得意先名 文字列
届け先郵便番号 届け先 送付先
届け先住所1
届け先住所2
請求先郵便番号 請求先 送付先
請求先住所1
請求先住所2
送付先
プロパティ名 型名
郵便番号 文字列
住所1 文字列
住所2 文字列

一方、データベーステーブルの複数のカラムを値オブジェクトで表すことがあります。これを埋め込みバリュー(Embedded Value)と呼びます。右表は埋め込みバリューの例で、得意先マスタテーブルのカラムと対応する得意先マスタクラスのプロパティの対応を表しています(実際の設計とは異なります)。得意先マスタクラスはエンティティ、届け先・請求先はともに送付先クラスの値オブジェクトです。例えば郵便番号が入力されたら郵便番号辞書を参照して対応する住所を住所1・住所2に設定するロジックを組み込むことを考えます。もし、単純にテーブルのカラムと対応したプロパティを持つクラスを設計すると、上記ロジックを2カ所に実装する必要があります。届け先と請求先を値オブジェクトとして設計するとロジックの実装は送付先クラス内の1カ所で済みます。また、別のテーブルで同じパターンが表われた場合にも再利用できます。

複数のカラムに対応する値オブジェクトを扱うときに問題になる点として、ドメイン層以外ではこのような構造のまま扱いにくいことがあります。Viewデータアクセス層ともに単純にカラムに対応している方が自然に扱えます。

NetFramework上で、上記問題を解決するため、エンティティの子である値オブジェクトが持つプロパティを、あたかもエンティティが直接持っているかのようにアクセスできるPropertyDescripterを作成し、これらPropertyDescripterのコレクションを通じてView、データアクセス層からアクセスするようにしました。Windows Formsのバインディング機構ともスムーズに連携できます。

値オブジェクトは不変オブジェクトまたは可変オブジェクトのどちらでも実装できます。どちらがよいかその得失を比較します。

不変オブジェクトと可変オブジェクト
不変オブジェクト 可変オブジェクト
参照コピー 参照をコピーしてもコピー元のオブジェクトが書き換わる心配がない 参照をコピーした後、コピー先オブジェクトのプロパティを変更すると、元のオブジェクトも書き換わる
共有 同じプロパティ値を持つオブジェクトを共有できる 共有できない
変更 一部のプロパティを変更するだけでも、オブジェクトを再作成する必要がある 一部のプロパティを変更する際に、それ以外の操作は不要
NetFrameworkとの対応 値型 および不変オプジェクトとして設計された参照型(※値型単体では可変だが他のオブジェクトに所有された値型のプロパティは変更できない) 可変オブジェクトとして設計された参照型

不変オブジェクトは参照コピーおよび共有の点で有利ですが、埋め込みバリューに応用する場合は一部のプロパティを変更しただけで再生成する必要がある点が不利です。このように動作するPropertyDescripterの実装が面倒になります。

そこで当方では埋め込みバリューを可変として実装しています。その代わりに埋め込みバリューを上位オブジェクトにコンポジット集約させて、一旦参照をコンストラクタで設定した後は参照を書き換えないことで意図しない変更を防いでいます。