贫血模型&充血模型

在领域驱动设计(DDD)和 Java 开发中,"贫血模型"(Anemic Domain Model) 是一个非常经典但也极具争议的概念。

简单来说,贫血模型就是:对象只包含数据(属性和 getter/setter),几乎不包含任何业务逻辑。


1. 贫血模型的特征

在一个典型的贫血模型设计中,你会看到大量的"纯数据对象":

  • 数据类 (POJO/DTO): 里面只有私有字段(private fields)和与之对应的 getter/setter 方法。
  • 业务逻辑类 (Service/Manager): 所有的业务逻辑、规则校验、状态变更都写在对应的 Service 层中。

代码示例:

java 复制代码
// 贫血的实体类
public class Order {
    private String status;
    private double amount;
    // 只有 getter 和 setter
}

// 充满逻辑的 Service 类
public class OrderService {
    public void pay(Order order) {
        if ("PAID".equals(order.getStatus())) {
            throw new RuntimeException("已支付");
        }
        order.setStatus("PAID"); // 逻辑操作在外部完成
        // 执行支付流程...
    }
}

2. 为什么贫血模型这么流行?

尽管在软件工程理论中它常被当作"反模式",但在实际生产中(尤其是 Spring + MyBatis 技术栈),它非常普遍,原因如下:

  1. 符合直觉: 数据和逻辑分离,对于新手来说,编写简单的增删改查(CRUD)非常直接。
  2. 工具链支持: 像 MyBatis、Hibernate 等 ORM 框架,以及 JSON 序列化库,都非常偏好这种只有字段的对象。
  3. 无状态逻辑: 这种设计使得业务逻辑层(Service)通常是无状态的,容易进行单元测试和横向扩展。
  4. 架构简单: 这种模式非常适合简单的 Web 应用,团队沟通成本低,代码结构高度标准化。

3. 贫血模型的缺陷(为什么它被诟病)

当业务逻辑变得极其复杂时,贫血模型会暴露出严重的问题:

  • 违反面向对象原则: 面向对象的核心是"封装"。贫血模型将数据暴露在外,让外部随意修改,这破坏了对象的完整性。
  • 逻辑分散(屎山预警): 随着业务增加,Service 层会变得极其臃肿。由于业务逻辑分散在各个 Service 中,很难搞清楚一个对象到底有哪些合法的状态转移。
  • 容易出错: 任何代码都可以随意调用 order.setStatus("PAID"),而不需要经过任何校验。对象的状态完全取决于调用者,而不是对象自己。
  • "过程式"编程: 本质上,这其实是披着面向对象外壳的过程式编程(Procedural Programming),它并没有真正发挥 OO 的威力。

4. 什么是"充血模型"?(与之对比)

与贫血模型相对的是充血模型(Rich Domain Model) 。在充血模型中,领域对象不仅包含数据,还包含行为(业务逻辑)

充血模型的改进:

java 复制代码
public class Order {
    private String status;
    private double amount;

    // 逻辑封装在实体内部
    public void pay() {
        if ("PAID".equals(this.status)) {
            throw new RuntimeException("已支付");
        }
        this.status = "PAID";
    }
}

在这种模式下,Service 层只需要调用 order.pay(),具体的业务规则由 Order 对象自己维护。


5. 如何平衡?(我的观点)

没有绝对的好坏,只有合适的场景。

  1. 小型项目 / 简单业务(CRUD):

    使用贫血模型是完全合理的。引入复杂的领域建模只会增加开发成本,导致"过度设计"。

  2. 复杂业务系统(金融、电商核心逻辑):

    必须转向充血模型。如果你的系统逻辑涉及复杂的状态机、多维度的校验,使用贫血模型会导致逻辑到处散落,最终变成难以维护的"大泥球"。

  3. 现实的折中:

    很多时候我们可以采用"混合模式":

    • 核心实体(Entity): 尽量充血,封装核心业务规则(比如 account.withdraw())。
    • 辅助类(DTO/VO): 保持贫血,仅用于数据传输和序列化。

总结一句话:

如果你发现你的 Service 类里充满了大量的 if-else 来判断某个对象的内部状态,那么你的代码正在向"贫血陷阱"滑落,此时就该考虑把逻辑下沉到实体对象中,让它成为一个真正"活的"对象,而不是一个冰冷的数据载体。

相关推荐
小箌2 小时前
springboot_02
java·spring boot·后端
一直都在5722 小时前
JSoup:Java 处理 HTML 的实用利器,从基础到实战爬取教程
java·python·html
重庆兔巴哥2 小时前
如何检查Java环境变量是否配置成功?
java·开发语言
_olone2 小时前
牛客每日一题:刷题统计(Java)
java·算法·容斥原理·牛客
junnhwan2 小时前
LeetCode Hot 100——栈
java·数据结构·算法·leetcode·hot 100
Gin3872 小时前
SpringBoot实现文件上传和下载
java·spring boot·后端
蓝天星空2 小时前
C# .net闭源与Java开源框架的对比
java·c#·.net
金牌归来发现妻女流落街头2 小时前
【用 Java API Client 操作 Elasticsearch】
java·elasticsearch·jenkins
Seven972 小时前
调试排错 - 线程Dump分析
java