4Implementing Domain-Driven Design

实体与值对象

领域对象

实体值对象领域事件规格说明

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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) {
        // 基于值比较
    }
}

四、本章小结

实体:有唯一标识,通过标识区分。

值对象:无标识,通过值区分,不可变。

领域事件:表示领域中发生的重要事情。

规格说明:封装可组合的业务规则。


五、思考与练习

在一个订单系统中识别实体和值对象。

为什么值对象应该是不可变的?有什么好处?

设计一个领域事件系统来处理订单确认后的操作。


4/7实体与值
0%