在软件设计中,贫血模型(Anemic Domain Model)和充血模型(Rich Domain Model)是两种截然不同的领域模型设计模式,尤其在领域驱动设计(DDD)中它们的区别至关重要。以下是详细解释和对比:
1. 贫血模型(Anemic Domain Model)
定义
- 数据与行为分离 :领域对象(如实体、值对象)仅包含数据属性(Getter/Setter),业务逻辑分散在服务层(Service类)中。
- 典型特征 :
- 领域对象是"哑巴数据容器"。
- 业务逻辑集中在Service类中,通过操作领域对象完成功能。
- 常见于传统分层架构(如Spring MVC + DAO模式)。
代码示例
java
// 贫血的Order类(仅有数据)
public class Order {
private String orderId;
private List<OrderItem> items;
private BigDecimal totalAmount;
private String status;
// Getter/Setter 方法(无业务逻辑)
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
// ... 其他Getter/Setter
}
// 业务逻辑集中在Service类
@Service
public class OrderService {
public void confirmOrder(Order order) {
if (order.getItems().isEmpty()) {
throw new IllegalStateException("订单不能为空");
}
order.setStatus("CONFIRMED");
orderRepository.save(order);
}
}
缺点
- 业务逻辑分散:规则散落在多个Service类中,难以维护。
- 领域对象无意义:对象仅是数据载体,无法表达业务意图。
- 容易产生重复代码:相同逻辑可能在多个Service中重复实现。
- 违背面向对象设计:对象失去封装性,成为"数据表映射工具"。
2. 充血模型(Rich Domain Model)
定义
- 数据与行为统一 :领域对象既包含数据属性,也封装业务逻辑。
- 核心原则 :
- 高内聚:对象的行为与数据紧密关联。
- 低耦合:通过方法暴露操作,隐藏内部实现细节。
- 符合面向对象设计:对象是"智能的",能自主完成职责。
代码示例
java
// 充血的Order类(数据 + 行为)
public class Order {
private String orderId;
private List<OrderItem> items;
private Money totalAmount;
private OrderStatus status;
// 业务逻辑内聚到聚合根
public void addItem(OrderItem item) {
if (status != OrderStatus.DRAFT) {
throw new IllegalStateException("已确认的订单不可修改");
}
items.add(item);
totalAmount = totalAmount.add(item.calculateTotal());
}
public void confirm() {
if (items.isEmpty()) {
throw new IllegalStateException("订单不能为空");
}
this.status = OrderStatus.CONFIRMED;
}
// Getter方法(无Setter,通过方法控制状态变更)
public String getOrderId() { return orderId; }
public List<OrderItem> getItems() { return Collections.unmodifiableList(items); }
}
// 应用服务仅协调流程
@Service
public class OrderApplicationService {
public void confirmOrder(String orderId) {
Order order = orderRepository.findById(orderId);
order.confirm(); // 业务逻辑在领域对象中
orderRepository.save(order);
}
}
优点
- 业务逻辑集中:规则内聚在领域对象中,易于理解和维护。
- 表达业务语义 :代码直接反映业务概念(如
order.confirm()
)。 - 减少重复代码:相同逻辑无需在多个Service中重复。
- 符合DDD原则:支持限界上下文、聚合等模式。
3. 贫血模型 vs. 充血模型
维度 | 贫血模型 | 充血模型 |
---|---|---|
业务逻辑位置 | 分散在Service类中 | 内聚在领域对象中 |
对象职责 | 数据容器 | 数据 + 行为 |
代码可维护性 | 低(逻辑分散) | 高(逻辑集中) |
面向对象设计 | 违背封装性 | 符合封装性 |
典型应用场景 | 简单CRUD系统 | 复杂业务系统(如电商、金融) |
4. 如何避免贫血模型?
设计原则
-
Tell, Don't Ask
对象应主动执行操作,而非被外部查询状态后修改。
java// ❌ 贫血模型:Service查询状态并修改 if (order.getStatus().equals("DRAFT")) { order.setStatus("CONFIRMED"); } // ✅ 充血模型:对象自己控制状态变更 order.confirm();
-
封装不变条件
将业务规则内聚到对象方法中,例如:
javapublic class BankAccount { private BigDecimal balance; public void withdraw(BigDecimal amount) { if (balance.compareTo(amount) < 0) { throw new InsufficientBalanceException(); } this.balance = balance.subtract(amount); } }
-
减少公开Setter
通过方法暴露操作,而非直接修改字段:
java// ❌ 允许外部随意修改状态 order.setStatus("CONFIRMED"); // ✅ 仅通过业务方法变更状态 order.confirm();
5. 充血模型的实践挑战
-
框架适配:JPA/Hibernate等ORM工具可能要求公开Setter或默认构造函数,可通过以下方式解决:
- 使用
protected
修饰符限制访问。 - 通过DTO与领域对象转换(如MapStruct)。
java@Entity public class OrderJpaEntity { // JPA要求字段可访问,但领域对象仍可封装逻辑 private String status; // 转换方法:将JPA实体转换为领域对象 public Order toDomain() { Order order = new Order(id); // 通过领域对象的方法恢复状态 if ("CONFIRMED".equals(status)) { order.confirm(); } return order; } }
- 使用
-
性能优化:避免因过度封装导致性能问题(如频繁加载关联对象),可通过延迟加载或CQRS解决。
总结
- 贫血模型是反模式,将数据与行为分离,导致代码臃肿和维护困难。
- 充血模型是DDD的核心实践,通过内聚业务逻辑到领域对象中,提升代码表达力和可维护性。
- 关键设计原则:封装业务规则 、减少公开Setter 、通过方法暴露操作。