根据DDD原则,人们经常建议使用价值对象来编码不具有自己生命周期但仅仅是价值的价值观。通过设计,这些对象是不可变的。它们经常替换原语,这使得代码更具语义和错误安全性。
理由非常明智,但有时会导致一些繁琐的操作。例如,考虑以下情况将地址编码为值对象的情况:
class Address extends ValueObject {
public Address(String line1, String line2, String postalCode, String String country) {
...
}
...
}
在应用程序中,用户只更改地址的一个字段并不罕见。为了在代码中实现这一点,人们必须做类似的事情:
String newCity = ...;
Address newAddress = new Address(
oldAddress.getLine1(),
oldAddress.getLine2(),
oldAddress.getPostalCode(),
newCity,
oldAddress.getCountry());
这可能导致一些非常重复且过于冗长的代码。在保留不可变值对象的同时避免这种情况的一些好策略是什么?
我自己的想法:
私人制定者,可以启用这样的辅助方法:
public Address byChangingPostalCode(String newPostalCode) {
Address newAddress = this.copy();
newAddress.setPostalCode(newPostalCode);
return newAdress;
}
缺点是该对象现在不再是不可变的,但只要它保持私有,就不应该成为问题,对吧......?
使值对象成为一个完整的实体。毕竟,需要在更长的时间内修改字段表明它确实具有生命周期。我不相信这一点,因为这个问题涉及开发人员的便利而不是域名设计。
我很高兴收到您的建议!
更新
感谢您的建议!我会选择私人制定者和纠正方法。
答案 0 :(得分:3)
让不可变值返回其他不可变值没有任何问题。
Address newAddress = oldAddress.correctPostalCode(...);
这是我在域模型中的首选方法。
另一种可能性是使用构建器
Address new Address = AddressBuilder.from(oldAddress)
.withPostalCode(...)
.build()
我不喜欢那个,因为build
并不是无处不在的语言的一部分。这是一个我更有可能在单元测试中使用的构造,我需要一个完整的地址来与API交谈,但测试本身只取决于细节的一部分。
答案 1 :(得分:1)
私有设置者,可以启用这样的辅助方法:
这是更改 Value对象的首选解决方案。方法的命名应该来自普遍存在的语言,但这可以结合到团队/编程语言惯例中。在PHP中有一个已知的约定:不可变命令方法名称以“with”开头。例如withCorrectedName($newName)
。
对象不能完全不变,只能这样做才能被认为是不可变的。
使值对象成为一个完整的实体。
它不再是ValueObject,所以不要!
答案 2 :(得分:0)
我会用派生方法来解决这个问题:
Address newAddress = oldAddress.derive( newPostalCode);
Address newAddress = oldAddress.derive( newLine1 );
等等......