首页/开发/代码质量与评审速查/手法:Replace Primitive with Object(以对象取代基本类型)
重构手法62 条

手法:Replace Primitive with Object(以对象取代基本类型)

为有约束的原始值建立值对象:金额带币种、电话号码带格式、温度带单位;校验、格式化和运算都有归属,类型错误在编译期或构造期即暴露。

以对象取代基本类型是什么

为有约束的原始值建立值对象:金额带币种、电话号码带格式、温度带单位。校验、格式化与运算都有了归属,类型错误在编译期或构造期就暴露,而不是等到运行期出现一个荒谬的数字。以类替换类型码是同一思路在枚举取值上的应用。

基本信息

适用场景原始值有格式、范围或单位约束,且被重复校验
实现要点构造时校验、不可变、实现相等性
配套手法以类替换类型码
相关坏味道基本类型偏执、数据泥团
收益类型即约束,调用方难以绕过

典型使用场景

  • 金额计算里币种不统一,导致重复换算
  • 电话号码与邮箱的判断分散在各层
  • 温度、长度、时长等单位混用
  • 身份证号、订单号这类有格式约束的标识
  • 类型码用整数表示,取值范围靠注释说明

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) { /* 先校验币种,再相加 */ }
}

实现要点

要点做法原因
构造校验构造或工厂方法里检查非法值无法进入领域
不可变字段私有且不提供修改方法相等性与缓存假设成立
相等性实现相等与哈希方法可用于集合去重与比较
运算内聚把加减比较搬进对象避免调用方取原始值自行计算
引入值对象之后要把运算搬进去:如果调用方仍取出原始值自行计算,校验与规则又散了出去,等于白建一层。
值对象不要做成可变,也不要放进哈希集合后再修改:相等性假设被破坏后,会出现去重失效、查不到元素这类难排查的问题。
转换放在边界上:接口层、数据库映射与配置读取处负责把原始值转成值对象,领域内部只使用值对象,避免两种表示在代码里混着用。

易错点

  • 引入对象后仍到处取原始值再计算:应把运算移进对象,否则封装没成立
  • 可变值对象参与哈希集合:相等性假设被破坏,去重与查找出现意外结果
  • 为每个字符串都建值对象:过度建模,转换代码的维护成本超过收益
  • 只改领域内部,序列化边界不转换:接口与数据库层自行拼装格式,问题留到运行期

数据来源与参考

  • 《重构》第 2 版 · 第 7 章(Replace Primitive with Object)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌