generated at
Value Object
1つのprimitive値や、値の合成物を、値(value)として扱うobject
objectというのは、OOPの文脈ではclass
FPの文脈ではnewtypeした値など
合成物であっても、primitive値と同等の扱いができればValueObject
個別にIdentityを持たずに、値のみを見て比較する

FPの文脈に慣れている人は、当たり前過ぎて逆に何言ってるかわからなくなりそうmrsekut
参照が絡む言語を説明にするために文章が長くなっている


FP言語向けには、以下の説明文で十分
永続的なidentityを
持つobjectはEntity
持たないobjectはValue Object
合成物かどうかももはや気にする必要はないmrsekut


なぜ必要になるか?
primitive値でも良いが、特に合成物を考えた方がわかりやすい
例えば、「xy座標」という概念を扱うことを考える
これは、 x座標 y座標 という2つの値の合成物である
例えば、 (1,2) (1,2) という2つのデータがあった時に、これらを等価と見なしたい
この、合成物同士の値だけを見て比較できれば、Value Objectの目的は達成している



FP的に表現すれば自然と等価になる
この Point はValue Objectと言える
hs
data Point = Point (Int, Int) deriving (Eq) Point (1,2) == Point (1,2) -- True
何も特殊なことはしていないmrsekut
合成物である必要もない
fs
let widgetCode1 = WidgetCode "W1234" let widgetCode2 = WidgetCode "W1234" printfn "%b" (widgetCode1 = WidgetCode2) // "true"
特別にequalの実装を加える必要はない
代数的データ型の構造が同じならば同じものだと判断される
必要であれば、equalの定義を変えることもできる
コレぐらい自明なので、Value Object#6285079f1982700000e1a831のような説明で済む



参照が関わる言語では、少し考えることが増える
以下の話は、言語に「構造を見て等価性をチェックできる機能があるかどうか」というだけmrsekut
構造だけを見た自明な比較をする演算子等が用意されていない
演算子 == === は、構造ではなく、参照の等価性を見ている
同じ構造の値を比較していても、参照が異なると true にならない
ts
const p1 = {x: 2, y: 3}; const p2 = {x: 2, y: 3}; p1 === p2 // false
従って、構造の等価性を判断する演算を自分で定義する必要がある
ts
const equalPoint = (p1: Point, p2: Point) => p1.x === p2.x && p1.y === p2.y
tsだとoverloadするわけにも行かないので、名前を変えて個別にそういう関数が必要
classなら、一律で equal() とかを用意しておけば良い
ts
class Point { constructor(private x: number, private y: number) {} equals(other: Point) { return this.x === other.x && this.y === other.y; } }
これで、複合物も値だけを見た比較ができるようになる
ts
const p1 = new Point(1,2); const p2 = new Point(1,2); p1.equals(p2) // true
以上で一応ValueObjectの定義は満たしている
しかし、これだけでは参照の取り扱いに難があるので、更に工夫を推奨される
参照を適当に扱うと、状態を変更した時に別部分も変更されるといったAliasing Bugが起きる
これを回避するためにimmutableにすると良い
値を変更するのではなく、新しく作成する

言語がそういう機能を用意するケースもある



間違いやすいポイント
ただprimitive値や合成物を包めばValueObjectになるわけではない
ただ値を包むだけでは、まだIdentityで比較する余地を残してしまう
包んだ上で、構造だけを見たobject同士の比較ができる必要がある
Value Objectは結果的にValueを包んだObjectにはなるが、考える順序が逆ということmrsekut
何でもかんでもprimitive値を全てclassでwrapしろという話ではない
「project内でIntを素のまま使うな、全部wrapしろ」というやつ
そもそもこれは、ValueObjectの定義というか、使い方の話なのであまり重要ではないmrsekut
必要であればすればいい
全部wrapするのが適切である状況もあると思う



具体例


付随的に得られる嬉しさ


参考
Martin Fowler
普通にめちゃくちゃわかりやすいので最初に読めば良いと思うmrsekut
F#なので、説明がシンプルで良い
Entityとの比較がメイン
具体的な実装方法については書かれていない
前半の説明が冗長に感じる。素直にValueObjectを読めば良い
後半は、誤解などについて










DDDではないっぽいが似たようなことが書かれているらしい

長い