领域建模战术:解密贫血模型(Anemic)与充血模型(Rich)的本质区别
在主流的企业级 Java 开发中,绝大多数项目都采用传统的“三层架构(Controller-Service-DAO)”。然而,在这种架构下,我们声明的领域对象(如 User、Order)通常只包含一堆简单的属性以及 Getter/Setter 方法,不包含任何业务逻辑。
这种**“数据与行为分离”**的对象模型,在领域驱动设计(DDD)中被称为**贫血模型(Anemic Domain Model)**。DDD 极力反对这种反面向对象的设计,主张将业务逻辑重新归还给对象本身,构建**充血模型(Rich Domain Model)**。
本文将系统拆解贫血模型与充血模型的本质区别、面向对象设计的丢失死穴、以及如何将贫血服务重构为高内聚充血实体的演进路径。
一、 核心范式对比:贫血模型 vs. 充血模型
两种领域模型在代码分工、面向对象特性以及可测试性上存在根本差异:
| 特征维度 | 贫血模型 (Anemic Model - 传统三层) | 充血模型 (Rich Model - DDD 战术) |
|---|---|---|
| 对象角色划分 | **哑存根**。实体仅作为纯粹的数据传输介质,不表达业务概念。 | **高内聚实体**。数据与针对该数据的业务行为封装在同一个类中。 |
| 业务逻辑归属 | 全部堆积在 XxxService 逻辑类中。导致 Service 极其臃肿(面向过程)。 | 封装在聚合根与实体内部。XxxService 只做轻量级的编排。 |
| 状态合法性守护 | 无。任何外部代码都能通过 setXxx() 强行修改对象状态,产生脏数据。 | **守护不变性(Invariants)**。必须通过聚合根的业务行为方法修改状态。 |
| 单元测试友好度 | 极差。测试业务逻辑必须 Mock 复杂的 Service 及其依赖的 DAO。 | **极强**。只需创建内存对象,直接断言其行为方法的输出,无需 Mock。 |
二、 业务代码流转拓扑重构演进
以“订单退款”为例,我们通过拓扑对比展示从贫血面向过程向充血面向对象的重构:
1. 贫血模型——面向过程的数据堆砌
[ OrderAppService ] ── 1. getAmount() / getStatus() ──> [ Order 实体 (Getter/Setter) ]
│
├─ 2. 在 Service 里写一堆 if-else 校验退款规则
├─ 3. 计算扣减金额,调用 setStatus(REFUNDED) 强行覆写
│
▼ 4. 调用 orderDao.update() 落盘
2. 充血模型——面向对象的方法调用
[ OrderAppService ] ── 1. 唯一入口: order.refund(refundAmount) ──> [ Order 聚合根 (充血实体) ]
│ │
│ ├─ 2. 内部校验状态与金额限制
│ └─ 3. 自身修改 status 属性并记录事件
▼ 4. 保存 orderRepository.save(order)
---三、 代码实战:将贫血订单服务重构为充血聚合根
以下展示了具体的重构代码对照,体现充血模型如何守护“订单退款”业务的不变性边界:
1. 重构前:传统的贫血订单服务(Anemic Service)
package com.company.sales.anemic;
/**
* 贫血实体:只有数据,没有行为
*/
public class SalesOrderPO {
private String orderId;
private String status; // PLACED, PAID, REFUNDED
private double payAmount;
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public double getPayAmount() { return payAmount; }
public void setPayAmount(double payAmount) { this.payAmount = payAmount; }
}
package com.company.sales.anemic;
public class OrderService {
private SalesOrderRepository repository;
/**
* 业务规则全部暴露在外,极易被绕过或漏写
*/
public void refund(String orderId, double refundAmount) {
SalesOrderPO order = repository.findById(orderId);
// 业务规则校验写在 Service,对象沦为只读容器
if (!"PAID".equals(order.getStatus())) {
throw new IllegalStateException("未付款订单无法退款!");
}
if (refundAmount > order.getPayAmount()) {
throw new IllegalArgumentException("退款金额不能大于实付金额!");
}
order.setStatus("REFUNDED"); // 属性被随意 set 修改
repository.update(order);
}
}
2. 重构后:高内聚的充血订单实体(Rich Model)
package com.company.sales.rich;
/**
* 充血实体:隐藏 Setter,通过业务方法控制状态演变
*/
public class SalesOrder {
private final String orderId;
private OrderStatus status;
private final double payAmount;
public SalesOrder(String orderId, double payAmount) {
this.orderId = orderId;
this.payAmount = payAmount;
this.status = OrderStatus.PLACED;
}
/**
* 充血核心业务行为:退款
* 不变性校验完全内聚在对象内部,任何外部调用无法绕过规则
*/
public void refund(double refundAmount) {
if (this.status != OrderStatus.PAID) {
throw new IllegalStateException("未付款订单无法退款!");
}
if (refundAmount > this.payAmount) {
throw new IllegalArgumentException("退款金额不能大于实付金额!");
}
this.status = OrderStatus.REFUNDED; // 隐式安全更新
}
public OrderStatus getStatus() { return status; }
}
---四、 总结
从贫血模型向充血模型的重构,是突破面向过程编程藩篱、回归面向对象设计本质的“必经之路”。
It 通过**关闭 Setter 属性修改接口,杜绝了系统状态在外部被任意篡改的隐患,强行守护了聚合根 of 不变性边界;并将复杂的业务计算规则全部收拢于高内聚 of 行为方法中,达成了领域逻辑与技术架构 of 彻底隔离**。掌握这套充血实体封装与贫血代码重构技术,是构建优雅、可演进式微服务骨架 of 核心看家本领!
本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。
评论交流 (0)
您尚未登录,请先 登录 后发表评论!



暂无评论
还没有人评论过本文,快来发表你的高见吧!