领域模型设计方法 充血模式和贫血模式怎么选

最近看了一下关于代码架构的一些书,想要融会贯通还需要实践的沉淀,虽然我们公司比较注重代码简洁之道的人还是偏少,但突破桎梏,方能成为至尊强者,废话说到这~

一、充血模式(Rich Domain Model)

定义:

充血模式是一种强调将业务逻辑封装在领域对象中的设计模式。领域对象不仅仅存储数据,还负责实现与自身相关的业务行为。

特点:

  1. 数据与行为封装:对象既有数据(属性),也有行为(方法),业务逻辑被封装在对象内部。
  2. 面向对象的特性:充分体现了面向对象设计的封装性和高内聚特性。
  3. 对象自治:对象对自己的状态负责,任何修改都通过对象自身的方法完成。
  4. 依赖外部服务少:尽可能减少对外部服务的依赖,业务逻辑主要集中在领域对象内部。

优势:

  • 高内聚:领域对象对自己的状态和行为负责,逻辑分散性较低。
  • 可维护性:业务逻辑集中在领域对象中,方便理解和修改。
  • 体现领域专家语言:代码与领域语言一致,更容易被领域专家理解。

劣势:

  • 复杂性高:需要设计合理的领域模型,前期的分析和开发成本较高。
  • 职责混乱的风险:如果不合理划分对象职责,可能导致领域对象过于复杂,成为“大对象”。

示例代码:

以“银行账户”为例:

public class BankAccount {
    private String accountNumber;
    private BigDecimal balance;

    public BankAccount(String accountNumber, BigDecimal initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }

    public void deposit(BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Deposit amount must be positive");
        }
        this.balance = this.balance.add(amount);
    }

    public void withdraw(BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Withdraw amount must be positive");
        }
        if (this.balance.compareTo(amount) < 0) {
            throw new IllegalStateException("Insufficient balance");
        }
        this.balance = this.balance.subtract(amount);
    }

    public BigDecimal getBalance() {
        return balance;
    }
}

特点depositwithdraw 方法封装了存款和取款的业务逻辑,直接体现业务规则。


二、贫血模式(Anemic Domain Model)

定义:

贫血模式是一种将领域对象设计为仅仅包含数据(属性),不包含业务逻辑的模式。业务逻辑被放在服务层或其他独立组件中。

特点:

  1. 数据与行为分离:领域对象只负责存储数据,不包含业务逻辑。
  2. 领域对象变得简单:领域对象更像是 DTO,主要是数据的载体。
  3. 业务逻辑集中在服务层:业务逻辑由独立的服务类负责实现。

优势:

  • 简单易用:领域对象设计简单,理解和开发成本较低。
  • 灵活性高:服务层集中处理业务逻辑,容易调整和扩展。
  • 符合传统分层架构:与很多开发者习惯的分层架构(如 Controller-Service-DAO)兼容。

劣势:

  • 低内聚:数据和逻辑分散在不同地方,增加维护难度。
  • 逻辑集中导致代码膨胀:服务类容易变得臃肿。
  • 弱化领域模型:领域模型不能很好地表达业务语义。

示例代码:

仍以“银行账户”为例:

public class BankAccount {
    private String accountNumber;
    private BigDecimal balance;

    // Getters and setters
    public String getAccountNumber() {
        return accountNumber;
    }

    public void setAccountNumber(String accountNumber) {
        this.accountNumber = accountNumber;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }
}

public class BankAccountService {
    public void deposit(BankAccount account, BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Deposit amount must be positive");
        }
        account.setBalance(account.getBalance().add(amount));
    }

    public void withdraw(BankAccount account, BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Withdraw amount must be positive");
        }
        if (account.getBalance().compareTo(amount) < 0) {
            throw new IllegalStateException("Insufficient balance");
        }
        account.setBalance(account.getBalance().subtract(amount));
    }
}

特点BankAccount 只是一个数据载体,业务逻辑完全放在 BankAccountService 中。


三、两者对比

特性充血模式贫血模式
职责分配数据和行为集中于领域对象内部数据和行为分离,逻辑在服务层
代码维护性高内聚,易于理解和维护低内聚,服务层代码容易变得复杂
复杂度初期设计复杂,需要充分理解业务较简单,适合快速开发
面向对象特性强调封装性、自治性,符合面向对象设计理念更接近传统分层架构,弱化领域对象的作用
适用场景复杂的业务逻辑和核心领域建模简单场景或对性能有极高要求的系统

四、实践建议

  1. 优先选择充血模式
    • 如果系统是核心领域系统,业务逻辑复杂且变化频繁,充血模式更适合。
    • 充血模式便于体现业务语义,有助于提升代码质量和可维护性。
  2. 选择贫血模式的场景
    • 如果系统主要以数据传递为主,例如 CRUD 型应用或数据导入导出系统。
    • 在性能敏感场景(如微服务高并发)中,贫血模式较为轻量,能够减少对象行为带来的开销。
  3. 结合两种模式
    • 轻充血模式:在贫血模型中适当增加行为,将与数据高度相关的业务逻辑封装到对象中,其他逻辑留在服务层。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值