设计模式学习笔记 贫血模型与充血模型
Posted 鮀城小帅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式学习笔记 贫血模型与充血模型相关的知识,希望对你有一定的参考价值。
1. 贫血模型
目前大部分业务系统都是基于MVC三层架构来开发的,而这种架构实际上是一种基于贫血模型的MVC三层架构开发模式。
虽然这种开发模式已经成为标准的 Web 项目的开发模式,但它却违反了面向对象编程风格,是一种彻彻底底的面向过程的编程风格,因此而被有些人称为
反模式(antipattern)
。
2. 解析MVC与贫血模型
MVC 三层架构中的 M 表示 Model,V 表示 View,C 表示 Controller。它将整个项目分为三层:展示层、逻辑层、数据层。MVC 三层开发架构是一个比较笼统的分层方式,很多项目也并不会100% 遵从 MVC 固定的分层方式。
比如,现在很多 Web 或者 App 项目都是前后端分离的,后端负责暴露接口给前端调用。这种情况下,我们一般就将后端项目分为 Repository 层、Service 层、Controller 层。其中,Repository 层负责数据访问,Service 层负责业务逻辑,Controller 层负责暴露接口。
举例:
我们平时开发 Web 后端项目的时候,基本上都是这么组织代码的。其中,UserEntity 和
UserRepository 组成了数据访问层,UserBo 和 UserService 组成了业务逻辑层,UserVo
和 UserController 在这里属于接口层。
UserBo 是一个纯粹的数据结构,只包含数据,不包含任何业务逻辑。业务逻辑集中在UserService 中。可以理解为Service 层的数据和业务逻辑,被分割为 BO 和 Service 两个类中。
像 UserBo 这样,只包含数据,不包含业务逻辑的类,就叫作
贫血模型
(Anemic Domain Model)。同理,UserEntity、UserVo 都是基于贫血模型设计的。这种贫血模型将数据与操作分离,破坏了面向对象的封装特性,是一种典型的面向过程的编程风格。
3. 充血模型
充血模型
(Rich Domain Model)正好相反,数据和对应的业务逻辑被封装到同一个类中。
这种充血模型满足面向对象
的封装特性,是典型的面向对象编程风格。
4. 领域驱动设计
领域驱动设计,即 DDD,主要是用来指导如何解耦业务系统,划分业务模块,定义业务领域模型及其交互。
基于充血模型的 DDD 开发模式实现的代码,也是按照 MVC 三层架构分层的。Controller 层还是负责暴露接口,Repository 层还是负责数据存取,Service 层负责核心业务逻辑。它跟基于贫血模型的传统开发模式的区别主要在 Service 层。
在基于充血模型的 DDD 开发模式中,Service 层包含 Service 类和 Domain 类两部分。Domain 就相当于贫血模型中的 BO。不过,Domain 与 BO 的区别在于它是基于充血模型开发的,既包含数据,也包含业务逻辑。而 Service 类变得非常单薄。总结一下的话就是,基于贫血模型的传统的开发模式,重 Service 轻 BO;基于充血模型的 DDD 开发模式,轻 Service 重Domain。
5. 贫血模型受欢迎的原因
几乎所有的 Web 项目,都是基于这种贫血模型的开发模式,甚至连 Java Spring 框架的官方 demo,都是按照这种开发模式来编写的。
原因可以分为三点:
- 大部分情况下,我们开发的系统业务,简单到就是基于SQL 的 CRUD 操作,所以,不需要动脑子精心设计充血模型;
- 充血模型的设计要比贫血模型更加有难度;
- 思维已固化,转型有成本
6. 什么情况下使用基于充血模型的DDD开发模式
在这种开发模式下,我们需要事先理清楚所有的业务,定义领域模型所包含的属性和方法。领域模型相当于可复用的业务中间层。新功能需求的开发,都基于之前定义好的这些领域模型来完成。
而
越复杂的系统,对代码的复用性、易维护性要求就越高,它更加适合使用基于充血模型的DDD开发模式。
7. 基于充血模型的DDD开发模式
基于充血模型的 DDD 开发模式跟基于贫血模型的传统开发模式相比,主要区别在 Service层。在基于充血模型的开发模式下,我们将部分原来在 Service 类中的业务逻辑移动到了一个充血的 Domain 领域模型中,让 Service 类的实现依赖这个 Domain 类。
在基于充血模型的 DDD 开发模式下,Service 类并不会完全移除,而是负责一些不适合放在 Domain 类中的功能。比如,负责与 Repository 层打交道、跨领域模型的业务聚合功能、幂等事务等非功能性的工作。
基于充血模型的 DDD 开发模式跟基于贫血模型的传统开发模式相比,Controller 层和Repository 层的代码基本上相同。这是因为,Repository 层的 Entity 生命周期有限,Controller 层的 VO 只是单纯作为一种 DTO。两部分的业务逻辑都不会太复杂。业务逻辑主要集中在 Service 层。所以,Repository 层和 Controller 层继续沿用贫血模型的设计思路是没有问题的。
public class VirtualWallet // Domain 领域模型 (充血模型)
private Long id;
private Long createTime = System.currentTimeMillis();;
private BigDecimal balance = BigDecimal.ZERO;
public VirtualWallet(Long preAllocatedId)
this.id = preAllocatedId;
public BigDecimal balance()
return this.balance;
public void debit(BigDecimal amount)
if (this.balance.compareTo(amount) < 0)
throw new InsufficientBalanceException(...);
this.balance.subtract(amount);
public void credit(BigDecimal amount)
if (amount.compareTo(BigDecimal.ZERO) < 0)
throw new InvalidAmountException(...);
this.balance.add(amount);
public class VirtualWalletService
// 通过构造函数或者 IOC 框架注入
private VirtualWalletRepository walletRepo;
private VirtualWalletTransactionRepository transactionRepo;
public VirtualWallet getVirtualWallet(Long walletId)
VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
VirtualWallet wallet = convert(walletEntity);
return wallet;
public BigDecimal getBalance(Long walletId)
return virtualWalletRepo.getBalance(walletId);
public void debit(Long walletId, BigDecimal amount)
VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
VirtualWallet wallet = convert(walletEntity);
wallet.debit(amount);
walletRepo.updateBalance(walletId, wallet.balance());
public void credit(Long walletId, BigDecimal amount)
VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
VirtualWallet wallet = convert(walletEntity);
wallet.credit(amount);
walletRepo.updateBalance(walletId, wallet.balance());
public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount)
//... 跟基于贫血模型的传统开发模式的代码一样...
以上是关于设计模式学习笔记 贫血模型与充血模型的主要内容,如果未能解决你的问题,请参考以下文章