简介
"用对象替换数据值"是深化领域驱动设计的重要重构手法。通过将原始数据组合封装为领域对象,可以建立更精确的业务模型,并集中处理相关数据操作。
针对的症状(代码坏味道)
- 多个相关联的原始值总是一起出现
- 需要维护数据间的约束关系
- 相同的数据组合在多处重复操作
- 存在表示业务实体的数据组
用对象替换数据值的详细步骤
- 识别数据组合
- 查找总是一起出现的原始值
- 标记具有业务含义的数据组
- 创建值对象类
- 声明final字段存储原始值
- 添加数据校验逻辑
- 迁移关联行为
- 将数据处理逻辑移至新类
- 保持值对象不可变性
- 替换原始引用
- 用新对象替换原始数据组合
- 更新相关方法签名
示例
重构前代码:
class Order {
private String customerName;
private String customerPhone;
String getContactInfo() {
return customerName + " " + customerPhone;
}
void validate() {
if (customerPhone.length() != 11) {
throw new IllegalArgumentException("Invalid phone");
}
}
}
重构步骤:
-
创建Customer值对象:
class Customer { private final String name; private final String phone; public Customer(String name, String phone) { if (phone.length() != 11) { throw new IllegalArgumentException("Invalid phone"); } this.name = name; this.phone = phone; } String getContactInfo() { return name + " " + phone; } } -
重构Order类:
class Order { private Customer customer; void validate() { // 验证逻辑移至Customer类 } }
练习
基础练习题
-
简单数据组合替换
// 重构前 class Product { private String category; private String skuCode; }
进阶练习题
-
带约束的数据对象
// 重构前 class BankAccount { void transfer(String targetAccount, String targetBank, double amount) { if (targetAccount.length() != 16) { throw new IllegalArgumentException("Invalid account"); } // 转账逻辑 } }
综合拓展练习题
-
复合业务对象
// 重构前 class DeliveryService { void schedule(String street, String city, LocalDateTime windowStart, LocalDateTime windowEnd) { if (windowEnd.isBefore(windowStart)) { throw new IllegalArgumentException("Invalid time window"); } // 排期逻辑 } }
代码审查要点
-
优点:
- 明确业务概念边界
- 集中数据约束规则
- 减少数据传递参数
-
潜在问题:
- 控制对象粒度大小
- 保持合理的构造成本
- 注意跨聚合的引用