特徴
値として扱うクラス
DB項目を扱う。
ただ、項目に対してビジネスロジックがない場合は基本データ型でも良い。
別インスタンスでも値が同じなら同じものと判断する。
普通のクラスと違って扱いはインスタンスというよりは値に着目する。別インスタンスでも値が同じなら同じものとみなす。
具体的な実装
普通にインスタンス同士を比較しても一致しないので比較メソッド(JavaやC#で言えば、Equalsメソッド)をオーバーライドしてそれを値が同じならtureを返すようにいじる。(なお、nullチェックはすること。値がnullだったら「false:インスタンスが一致しない」を返す。後、「==」や「!=」メソッド(staticメソッド)についてもケアすること。)
ただ、これを全てのValueObjectに対して記述していくのはめんどくさいと思うので抽象クラス(Abstract)として基底クラスを作って共通化するようにしましょう。「ValueObject」というクラスを別途作って継承させます。
なお、比較対象の
不変にする。(絶対に「完全コンストラクタパターン」(readonlyなクラス)とする。)
コンストラクタで初期化するタイミングだけが値がセットされて2度と変わらない。もし、値を変えたくなった場合は別インスタンスで生成し直す作りにします。readonlyなので、セッターは作ってはいけません。
なぜか?
これはイミュータブルであることによってバグが混在されにくくなるためです。並行、並列処理などを使う場合などはそれが顕著になりますが、不変なオブジェクトになっていることによって安心して扱うことができます。ただ、デメリットとしてインスタンスを使いまわせないのでパフォーマンスに影響が出る可能性がある点です。ただ、バグが混入する可能性を減らす方が良いでしょう。
フィールドを更新するメソッドを作ったときはどうするか?
例えば、名前クラスで命名変更メソッドみたいなものを作ったとして場合はそのまま書くとフィールドを変更することになってしまいます。そうしないために新しく別の値を設定して初期化したもので自分自身のオブジェクトをnewして新しいインスタンスとして返却するようにします。
値とロジックを一体化させる。
継承はさせない。
メリット
- 基本データ型で値を定義する場合に比べてロジックが散らばりにくい。
- 基本データ型で値を定義する場合に比べてバグが発生しにくい。
ValueObjectの作成判断
ではこういう場合はどうでしょうか?
1 |
FullNameクラスがあって、FirstNameとLastNameをバリューオブジェクト化するかどうか。 |
ここまで細かい粒度までクラス化していたらさすがにやりすぎと思う部分もあるかもしれません。
判断基準
では、判断基準としては下記が良いでしょう。
- ルールが存在しているか
- それ単体で取り扱いたいか。
ルール
例えば、FirstNameやLastNameだけの独自ルールがあるかどうかなどで判断します。
それ単体で取り扱いたいか
例えば、FirstNameやLastNameを単体でシステム上取り扱うケースはあるかどうかなどです。
代替案
仮にオブジェクト化しなかったとしても、コンストラクタ内でチェックすることは可能です。
ValueObjectの作成タイミング
DBなどから値を取得したらなるべく早く作成するのが吉です。
区分のValueObject
区分(例えば、0:晴れ、1:曇り、2:雨など)は少し特殊です。
メンバ
区分値(固定値)
staticなメンバとして自身のValueObjectをnewして持たせます。(2度と変わらない。)
なお、区分ごとに一つのメンバにします。(例えば、晴れならSunny変数、曇りならCloud変数など)
こうすることで外部から使う時も別に固定値ファイルとかを作らずにクラスからそのままメンバを参照できます。(もちろんクラス内部からも直接参照できます。)
選択値(可変値)
インスタンス変数として選択値を持たせます。場合によっては区分値(固定値)と比較する処理などを作ったりもします。
この記事へのコメントはありません。