在测试驱动开发的环境下,当设计一个复杂业务中的值对象时,应当如何确保其不可变性以及等同性比较的方式正确无误?给出一个具体的实现例子。

在测试驱动开发(TDD)的环境下,确保值对象的不可变性和等同性比较的正确无误是至关重要的。值对象的不可变性意味着一旦对象创建后其状态就不能被改变,等同性则表示值对象的内容相同即视为等价,而不考虑对象的身份标识。下面通过一个具体的Java实现例子来说明如何实现这些特性。

定义值对象

首先,我们定义一个表示货币金额的值对象Money,它包含金额数量amount和货币类型currency两个属性。

public final class Money {
    private final BigDecimal amount;
    private final String currency;

    public Money(BigDecimal amount, String currency) {
        if (amount == null || currency == null) {
            throw new IllegalArgumentException("Amount and currency cannot be null");
        }
        this.amount = amount;
        this.currency = currency;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public String getCurrency() {
        return currency;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Money money = (Money) o;
        return amount.equals(money.amount) &&
               currency.equals(money.currency);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }

    @Override
    public String toString() {
        return "Money{" +
               "amount=" + amount +
               ", currency='" + currency + '\'' +
               '}';
    }
}

分析

  • 构造函数Money类的构造函数是私有的,只在创建对象时接受参数,且对空值进行了保护,保证了对象创建后的不可变性。
  • 访问器方法:提供只读的访问方法,无任何setter或修改状态的方法,保持了不可变性。
  • equals和hashCode:重写了equalshashCode方法,确保当两个Money对象的amountcurrency属性完全相同时,它们被认为是等同的。equals方法首先检查是否为同一个对象引用,然后检查对象类型,最后比较属性值。hashCode方法根据属性值计算哈希值,保证了在哈希集合中能够正确地分辨不同的值对象。
  • toString:重写toString方法便于调试和日志记录。

通过这种方式,Money值对象既保持了不可变性,又正确实现了等同性比较。在TDD的过程中,应当为这些方法编写相应的单元测试,确保它们的行为符合预期。例如,可以编写测试用例验证两个相同属性的Money对象确实在equals方法中返回true,以及它们的hashCode值是否相同。