第4章:实体与值对象(Entities and Value Objects)
一、导读
1.1 本章学习目标
第4章讲解 DDD 的两种基本构建块——实体和值对象。
本章的核心学习目标包括:
- 理解实体和值对象的区别
- 掌握值对象的设计原则
- 学会使用领域事件
- 理解规格说明(Specification)模式
1.2 为什么本章如此重要
实体和值对象是领域模型的基本构建块。正确区分它们,能显著提高模型的表现力和代码的可维护性。值对象的不可变性让并发编程变得简单,实体的身份标识让业务对象的追踪变得清晰。
1.3 核心问题引导
实体和值对象的本质区别是什么?
为什么值对象应该是不可变的?
领域事件在 DDD 中扮演什么角色?
规格说明模式解决了什么问题?
二、核心概念详解
2.1 实体
java
// 实体:有唯一标识,通过标识来区分
public class Customer {
private CustomerId id; // 唯一标识
private CustomerName name;
private EmailAddress email;
// 通过 ID 判断相等性
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Customer)) return false;
Customer other = (Customer) o;
return id.equals(other.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}2.2 值对象
java
// 值对象:没有标识,通过属性值来区分
// 不可变!
public final class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
// 通过值判断相等性
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money other = (Money) o;
return amount.compareTo(other.amount) == 0
&& currency.equals(other.currency);
}
// 行为方法返回新实例
public Money add(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(amount.add(other.amount), currency);
}
public Money multiply(int quantity) {
return new Money(amount.multiply(BigDecimal.valueOf(quantity)), currency);
}
}2.3 领域事件
java
// 领域事件:表示领域中发生的重要事情
public class OrderPlaced implements DomainEvent {
private final OrderId orderId;
private final CustomerId customerId;
private final Money totalAmount;
private final Instant occurredAt;
// 不可变
}
// 在聚合中发布事件
public class Order {
private List<DomainEvent> events = new ArrayList<>();
public void confirm() {
// ... 业务逻辑
events.add(new OrderConfirmed(id, customerId, totalAmount()));
}
public List<DomainEvent> getEvents() {
return Collections.unmodifiableList(events);
}
public void clearEvents() {
events.clear();
}
}
// 事件处理
public class OrderConfirmedHandler {
public void handle(OrderConfirmed event) {
// 发送通知
// 更新库存
// 记录审计日志
}
}2.4 规格说明
java
// 规格说明:封装业务规则
public interface Specification<T> {
boolean isSatisfiedBy(T candidate);
}
// 可组合的规格
public class OverdueOrderSpecification implements Specification<Order> {
private final LocalDate today;
public OverdueOrderSpecification(LocalDate today) {
this.today = today;
}
@Override
public boolean isSatisfiedBy(Order order) {
return order.isPending() && order.getDueDate().isBefore(today);
}
}
// 使用
Specification<Order> overdue = new OverdueOrderSpecification(LocalDate.now());
List<Order> overdueOrders = orders.stream()
.filter(overdue::isSatisfiedBy)
.collect(Collectors.toList());三、实战案例
3.1 案例一:地址值对象
java
public final class Address {
private final String street;
private final String city;
private final String state;
private final String zipCode;
private final String country;
public Address(String street, String city, String state, String zipCode, String country) {
this.street = validate(street, "Street");
this.city = validate(city, "City");
this.state = validate(state, "State");
this.zipCode = validateZip(zipCode);
this.country = validate(country, "Country");
}
// 不可变,修改返回新实例
public Address withCity(String newCity) {
return new Address(street, newCity, state, zipCode, country);
}
// 业务行为
public boolean isDomestic() {
return country.equals("US");
}
@Override
public boolean equals(Object o) {
// 基于值比较
}
}四、本章小结
实体:有唯一标识,通过标识区分。
值对象:无标识,通过值区分,不可变。
领域事件:表示领域中发生的重要事情。
规格说明:封装可组合的业务规则。
五、思考与练习
在一个订单系统中识别实体和值对象。
为什么值对象应该是不可变的?有什么好处?
设计一个领域事件系统来处理订单确认后的操作。