以对象取代基本类型是什么
为有约束的原始值建立值对象:金额带币种、电话号码带格式、温度带单位。校验、格式化与运算都有了归属,类型错误在编译期或构造期就暴露,而不是等到运行期出现一个荒谬的数字。以类替换类型码是同一思路在枚举取值上的应用。
基本信息
| 适用场景 | 原始值有格式、范围或单位约束,且被重复校验 |
|---|---|
| 实现要点 | 构造时校验、不可变、实现相等性 |
| 配套手法 | 以类替换类型码 |
| 相关坏味道 | 基本类型偏执、数据泥团 |
| 收益 | 类型即约束,调用方难以绕过 |
典型使用场景
- 金额计算里币种不统一,导致重复换算
- 电话号码与邮箱的判断分散在各层
- 温度、长度、时长等单位混用
- 身份证号、订单号这类有格式约束的标识
- 类型码用整数表示,取值范围靠注释说明
Money 值对象
Money 值对象
final class Money {
private final BigDecimal amount;
private final Currency currency;
Money(BigDecimal amount, Currency currency) {
this.amount = amount.setScale(2, RoundingMode.HALF_UP);
this.currency = currency;
}
Money plus(Money other) { /* 先校验币种,再相加 */ }
}实现要点
| 要点 | 做法 | 原因 |
|---|---|---|
| 构造校验 | 构造或工厂方法里检查 | 非法值无法进入领域 |
| 不可变 | 字段私有且不提供修改方法 | 相等性与缓存假设成立 |
| 相等性 | 实现相等与哈希方法 | 可用于集合去重与比较 |
| 运算内聚 | 把加减比较搬进对象 | 避免调用方取原始值自行计算 |
引入值对象之后要把运算搬进去:如果调用方仍取出原始值自行计算,校验与规则又散了出去,等于白建一层。
值对象不要做成可变,也不要放进哈希集合后再修改:相等性假设被破坏后,会出现去重失效、查不到元素这类难排查的问题。
转换放在边界上:接口层、数据库映射与配置读取处负责把原始值转成值对象,领域内部只使用值对象,避免两种表示在代码里混着用。