实体与值对象
实体由身份标识区分:订单号相同即视为同一订单,即使其他属性全变了仍是同一个对象,它需要表达生命周期与状态变化。值对象由属性值区分且创建后不可变:金额、地址、时间区间、坐标都属于这一类,相等性按全部属性比较。不可变的值对象可以安全共享、自由复制、作为缓存键,也便于把校验规则封装在自身内部。
对比
| 维度 | 实体 | 值对象 |
|---|---|---|
| 判别依据 | 有生命周期与身份标识 | 只由属性值定义 |
| 可变性 | 状态随时间变化 | 创建后不可变,需要变更就创建新值 |
| 示例 | 订单、客户、设备、账号 | 金额、坐标、日期区间、联系方式 |
| 相等性 | 按标识比较 | 按全部属性比较 |
| 持久化 | 有独立标识与主键 | 通常内嵌在实体中,随宿主一起保存 |
基本信息
| 值对象要点 | 不可变、自校验、无副作用方法、可安全共享 |
|---|---|
| 常见误区 | 把金额做成裸数字或字符串,货币与精度无处表达 |
| 标识生成 | 业务标识(订单号)或技术标识(UUID),二者需区分 |
| 相等性实现 | 实体比较标识,值对象比较全部字段,不可混用 |
| 适用语言 | Java 用 record 或不可变类,C# 用 record,Python 用 dataclass(frozen=True) |
典型使用场景
- 金额与货币:把数量、货币单位、精度封装在一起,避免单位混用
- 地址信息:收货地址不可变,修改等于更换新地址,便于审计
- 时间区间:起止时间成对出现且需要校验先后顺序
- 编号与编码:订单号、设备编码封装校验规则,杜绝非法值
- 领域规则收敛:把“金额不能为负”这类校验放进值对象构造
- 测试简化:值对象相等即可比,断言更直观
值对象不可变意味着“修改”会创建新对象,因此需要注意在高频路径上避免过度分配;多数业务场景下可读性收益远大于开销。
把值对象的字段直接暴露为公共可变属性,等于放弃了自校验与不可变性,规则会被绕过。