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

在软件设计中,贫血模型(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通过方法暴露操作
相关推荐
洛神灬殇17 分钟前
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
数据库·redis·后端
雷渊1 小时前
DDD经典的四层架构和洋葱架构、六边形架构区别
后端
SimonKing1 小时前
【Spring Boot配置终极指南】1分钟让你精准指定配置文件,使应用部署游刃有余!
java·后端
雷渊1 小时前
深入分析理解洋葱架构
后端
程序媛学姐1 小时前
SpringBoot Actuator健康检查:自定义HealthIndicator
java·spring boot·后端
程序媛学姐1 小时前
SpringBoot Actuator指标收集:Micrometer与Prometheus集成
spring boot·后端·prometheus
欲儿1 小时前
RabbitMQ原理及代码示例
java·spring boot·后端·rabbitmq
林 子1 小时前
Spring Boot自动装配原理(源码详细剖析!)
spring boot·后端
编程轨迹1 小时前
使用 Spring 和 Redis 创建处理敏感数据的服务
后端
未完结小说2 小时前
服务注册与发现(nacos)
后端