不可变值对象的变异操作策略

时间:2017-10-03 09:51:54

标签: domain-driven-design immutability

根据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());

这可能导致一些非常重复且过于冗长的代码。在保留不可变值对象的同时避免这种情况的一些好策略是什么?

我自己的想法:

  1. 私人制定者,可以启用这样的辅助方法:

    public Address byChangingPostalCode(String newPostalCode) {
        Address newAddress = this.copy();
        newAddress.setPostalCode(newPostalCode);
        return newAdress;
    }
    

    缺点是该对象现在不再是不可变的,但只要它保持私有,就不应该成为问题,对吧......?

  2. 使值对象成为一个完整的实体。毕竟,需要在更长的时间内修改字段表明它确实具有生命周期。我不相信这一点,因为这个问题涉及开发人员的便利而不是域名设计。

  3. 我很高兴收到您的建议!

    更新

    感谢您的建议!我会选择私人制定者和纠正方法。

3 个答案:

答案 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 );

等等......