聊一聊贫血模型和充血模型区别

在软件设计中,贫血模型(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. 如何避免贫血模型?

设计原则

  1. Tell, Don't Ask

    对象应主动执行操作,而非被外部查询状态后修改。

    java 复制代码
    // ❌ 贫血模型:Service查询状态并修改
    if (order.getStatus().equals("DRAFT")) {
        order.setStatus("CONFIRMED");
    }
    
    // ✅ 充血模型:对象自己控制状态变更
    order.confirm();
  2. 封装不变条件

    将业务规则内聚到对象方法中,例如:

    java 复制代码
    public class BankAccount {
        private BigDecimal balance;
        
        public void withdraw(BigDecimal amount) {
            if (balance.compareTo(amount) < 0) {
                throw new InsufficientBalanceException();
            }
            this.balance = balance.subtract(amount);
        }
    }
  3. 减少公开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通过方法暴露操作
相关推荐
uzong2 小时前
技术故障复盘模版
后端
GetcharZp2 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程2 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研2 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi3 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国4 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy4 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack4 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9655 小时前
pip install 已经不再安全
后端
寻月隐君5 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github