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

在软件设计中,贫血模型(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通过方法暴露操作
相关推荐
Vfw3VsDKo43 分钟前
Maui 实践:Go 接口以类型之名,给 runtime 传递方法参数
开发语言·后端·golang
是真的小外套2 小时前
第十五章:XXE漏洞攻防与其他漏洞全解析
后端·计算机网络·php
ybwycx4 小时前
SpringBoot下获取resources目录下文件的常用方法
java·spring boot·后端
小陈工4 小时前
Python Web开发入门(十一):RESTful API设计原则与最佳实践——让你的API既优雅又好用
开发语言·前端·人工智能·后端·python·安全·restful
小阳哥AI工具4 小时前
Seedance 2.0使用真人参考图生成视频的方法
后端
IeE1QQ3GT4 小时前
使用ASP.NET Abstractions增强ASP.NET应用程序的可测试性
后端·asp.net
Full Stack Developme5 小时前
SpringBoot多线程池配置
spring boot·后端·firefox
sxhcwgcy7 小时前
SpringBoot 使用 spring.profiles.active 来区分不同环境配置
spring boot·后端·spring
稻草猫.9 小时前
Spring事务操作全解析
java·数据库·后端·spring
希望永不加班9 小时前
SpringBoot 整合 MongoDB
java·spring boot·后端·mongodb·spring