贫血模型&充血模型

在领域驱动设计(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 来判断某个对象的内部状态,那么你的代码正在向"贫血陷阱"滑落,此时就该考虑把逻辑下沉到实体对象中,让它成为一个真正"活的"对象,而不是一个冰冷的数据载体。

相关推荐
2601_962062945 分钟前
Spring Boot入门——Spring Boot项目的创建
java·数据库·spring boot
一嘴一个橘子1 小时前
springmvc 全局异常处理【补充】
java
Wang's Blog1 小时前
Vibe Coding一人即团队系列35: 基于Claude Code的Spring Boot项目初始化实践
java·spring boot·后端
Dovis(誓平步青云)1 小时前
拍视频前先把镜头想清楚:做一个分镜取景辅助器
android·java·服务器·javascript·人工智能
AI人工智能+电脑小能手1 小时前
大白话说Java设计模式-45-解释器模式(源码剖析篇)
java·设计模式·解释器模式·源码分析·pattern·spel·javacc
吴声子夜歌2 小时前
Java——开发中通用的方法和准则(二)
java·开发语言·php
袁震2 小时前
HarmonyOS 应用包体积优化与上架自检实战:从 76.2MB 到 3.6MB
java·华为·性能优化·harmonyos
zcmodeltech2 小时前
反应装置模型控制系统设计与实现:多设备协同联动方案
java·网络·数据库·stm32·嵌入式硬件·能源·制造
码视野2 小时前
基于 Spring Boot + Vue3 的【城市地下燃气管网微泄漏感知与相邻地下空间燃爆预警中台】设计与实现(含PRD/三端高保真源码/大屏)
java·前端·人工智能·spring boot·后端
2601_962071573 小时前
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
java·开发语言·数据库