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