什么是值对象(Value Object)?如何设计值对象?

  • Post category:Python

值对象(Value Object)是指没有唯一标识符(Identifier)和可变状态(Mutability)的对象。它们是用于描述和处理特定概念的对象,例如日期、时间、货币金额、颜色等。具有一些特定的属性,如可组合、可比较性、不变性、建立在严格的类型约束基础上。在领域驱动设计中,值对象是建立在领域边界上的概念,是作为领域实体的补充,帮助开发者将复杂设计分解为彼此独立的部分。

设计值对象

在设计值对象时,需要注意以下三个方面:

不变性(Immutability)

值对象应该是不可变的,也就是一旦创建,就不能够改变其中的属性。这样可以保证值对象作为领域模型的一部分,具有确定行为和意图。

例如,一个货币金额对象需要指定货币类型和具体金额数值。一旦货币金额对象被创建,金额数值不能再被修改,否则会产生不一致性。

public class Money {
    private final Currency currency;
    private final double amount;

    public Money(Currency currency, double amount) {
        this.currency = currency;
        this.amount = amount;
    }
    // 省略其他方法
}

可比较性(Comparability)

值对象还有一个重要特性是可比较性,我们可以通过比较两个值对象是否相等,来判断它们是否表示相同的概念。

例如,当用户注册时需要使用手机号码作为唯一标识符。我们可以创建一个名为 PhoneNumber 的值对象来表示电话号码。

public class PhoneNumber {
    private final String number;

    public PhoneNumber(String number) {
        this.number = number;
    }

    // 覆盖 equals 方法,判断 PhoneNumber 是否相等
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof PhoneNumber)) return false;
        PhoneNumber that = (PhoneNumber) o;
        return Objects.equals(number, that.number);
    }
    // 覆盖 hashCode 方法
    @Override
    public int hashCode() {
        return Objects.hash(number);
    }
}

可组合性(Composability)

值对象应该可以组合成更高级别的概念,从而构建更复杂的领域模型。

例如,订单可能包含多个货物,每个货物都是一个具有名称、单价、数量的值对象。

public class OrderLine {
    private final String itemName;
    private final Money price;
    private final int quantity;

    public OrderLine(String itemName, Money price, int quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
    // 省略其他方法
}

示例说明

日期时间值对象

// LocalDate 日期时间值对象
public final class LocalDate {
    // 省略属性和方法
    @Override
    public boolean equals(Object o) {
        // 省略代码
    }
    @Override
    public int hashCode() {
        // 省略代码
    }
    // other methods
}

// LocalTime 日期时间值对象
public final class LocalTime {
    // 省略属性和方法
    @Override
    public boolean equals(Object o) {
        // 省略代码
    }
    @Override
    public int hashCode() {
        // 省略代码
    }
    // other methods
}

// LocalDateTime 日期时间值对象
public final class LocalDateTime {
    // 省略属性和方法
    @Override
    public boolean equals(Object o) {
        // 省略代码
    }
    @Override
    public int hashCode() {
        // 省略代码
    }
    // other methods
}

带保留小数的货币金额值对象

public final class Money {
    private final BigDecimal amount;

    public Money(BigDecimal amount) {
        this.amount = amount.setScale(2, RoundingMode.HALF_UP);
    }

    public Money add(Money other) {
        return new Money(amount.add(other.amount));
    }

    public Money subtract(Money other) {
        return new Money(amount.subtract(other.amount));
    }

    public Money multiply(int factor) {
        return new Money(amount.multiply(BigDecimal.valueOf(factor)));
    }

    public Money divide(int divisor) {
        return new Money(amount.divide(BigDecimal.valueOf(divisor)));
    }

    @Override
    public boolean equals(Object o) {
        // 省略代码
    }

    @Override
    public int hashCode() {
        // 省略代码
    }

    // other methods
}

这样设计值对象可以方便代码的重构和维护,也可以使代码更加模块化,便于进行单元测试和领域建模。同时,使用不同的值对象可以将领域模型中的概念清晰地表示出来,使代码更容易理解和阅读。