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

在软件设计中,贫血模型(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通过方法暴露操作
相关推荐
懵逼的小黑子37 分钟前
Django 项目的 models 目录中,__init__.py 文件的作用
后端·python·django
小林学习编程2 小时前
SpringBoot校园失物招领信息平台
java·spring boot·后端
java1234_小锋4 小时前
Spring Bean有哪几种配置方式?
java·后端·spring
柯南二号5 小时前
【后端】SpringBoot用CORS解决无法跨域访问的问题
java·spring boot·后端
每天一个秃顶小技巧6 小时前
02.Golang 切片(slice)源码分析(一、定义与基础操作实现)
开发语言·后端·python·golang
gCode Teacher 格码致知7 小时前
《Asp.net Mvc 网站开发》复习试题
后端·asp.net·mvc
Moshow郑锴8 小时前
Spring Boot 3 + Undertow 服务器优化配置
服务器·spring boot·后端
Chandler249 小时前
Go语言即时通讯系统 开发日志day1
开发语言·后端·golang
有梦想的攻城狮9 小时前
spring中的@Lazy注解详解
java·后端·spring
野犬寒鸦10 小时前
Linux常用命令详解(下):打包压缩、文本编辑与查找命令
linux·运维·服务器·数据库·后端·github