广告
您当前的位置: 首页 >  技术 >  编程开发

领域建模战术:解密贫血模型(Anemic)与充血模型(Rich)的本质区别

作者:CoderWang 时间:2026-07-07 阅读数:0人阅读

在主流的企业级 Java 开发中,绝大多数项目都采用传统的“三层架构(Controller-Service-DAO)”。然而,在这种架构下,我们声明的领域对象(如 UserOrder)通常只包含一堆简单的属性以及 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)

正在加载评论...
头像

CoderWang

当你还撑不起你的梦想时,就要去奋斗。如果缘分安排我们相遇,请不要让她擦肩和过。我们一起奋斗!

微信