本文来源回看一下领域模型中的贫血模型
本篇笑点
我要从甲方跳到乙方公司了。
你得知道这篇讲的些什么
本文 1517 字,阅读可能需要 2 分 59 秒
Java Web 开发中最常见的领域模型 - 贫血模式
说明
前几个月和杭州的大妖怪说要做一个 xxxx 项目,其中聊到了领域模型。
准备写一篇领域模型回顾篇,基本是针对网络上的总结进行一些细节丰富,去除一些不必要的理解。
贫血模式
项目要采用的是 贫血模型,我们先来看一下贫血模型。
而所谓 贫血模型 就是模型对象之间存在完整的关联(可能存在多余的关联),但是对象除了 get 和 set 方外外几乎就没有其它的方法,整个对象充当的就是一个数据容器。
所有的业务方法都在一个无状态的 Service 类中实现,Service 类仅仅包含一些行为。
这是大多数 JavaWeb 程序采用的最常用开发模型,看完下面的包结构你会更清晰这个模型。
包结构
贫血模型的实现一般包括如下包:
包 | 内容 |
---|---|
dao | 负责持久化逻辑 |
model | 包含数据对象,是 service 操纵的对象 |
service | 放置所有的服务类,其中包含了所有的业务逻辑 |
facade | 提供对 UI 层访问的入口 |
代码实现
先看 model 包的两个类,Account 和 TransferTransaction 对象,分别代表帐户和一次转账事务。由于它们不包含业务逻辑,就是一个普通的 Java Bean,下面的代码省略了 get 和 set 方法。
/**
* 账户
**/
public class Account {
private String accountId;
private BigDecimal balance;
public Account() {}
public Account(String accountId, BigDecimal balance) {
this.accountId = accountId;
this.balance = balance;
}
// getter and setter ....
}
/**
* 转账
**/
public class TransferTransaction {
private Date timestamp;
private String fromAccountId;
private String toAccountId;
private BigDecimal amount;
public TransferTransaction() {}
public TransferTransaction(String fromAccountId, String toAccountId, BigDecimal amount, Date timestamp) {
this.fromAccountId = fromAccountId;
this.toAccountId = toAccountId;
this.amount = amount;
this.timestamp = timestamp;
}
// getter and setter ....
}
这两个类很常见,就是数据容器。
接下来看 service 包中 TransferService 接口和它的实现 TransferServiceImpl。TransferService 定义了转账服务的接口,TransferServiceImpl 则提供了转账服务的实现。
public interface TransferService {
TransferTransaction transfer(String fromAccountId, String toAccountId, BigDecimal amount)
throws AccountNotExistedException, AccountUnderflowException;
}
public class TransferServiceImpl implements TransferService {
private AccountDAO accountDAO;
private TransferTransactionDAO transferTransactionDAO;
public TransferServiceImpl(AccountDAO accountDAO,
TransferTransactionDAO transferTransactionDAO) {
this.accountDAO = accountDAO;
this.transferTransactionDAO = transferTransactionDAO;
}
/**
* 转账逻辑
* @param fromAccountId 转账人
* @param toAccountId 收账人
* @param amount 金额
* @return
* @throws AccountNotExistedException
* @throws AccountUnderflowException
*/
public TransferTransaction transfer(String fromAccountId, String toAccountId,
BigDecimal amount) throws AccountNotExistedException, AccountUnderflowException {
Validate.isTrue(0 < amount.compareTo(BigDecimal.ZERO));
Account fromAccount = accountDAO.findAccount(fromAccountId);
if (null == fromAccount) throw new AccountNotExistedException(fromAccountId);
if (0 > fromAccount.getBalance().compareTo(amount)) {
throw new AccountUnderflowException(fromAccount, amount);
}
Account toAccount = accountDAO.findAccount(toAccountId);
if (null == toAccount) throw new AccountNotExistedException(toAccountId);
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountDAO.updateAccount(fromAccount); // 对Hibernate来说这不是必须的
accountDAO.updateAccount(toAccount); // 对Hibernate来说这不是必须的
return transferTransactionDAO.create(fromAccountId, toAccountId, amount);
}
}
TransferServiceImpl 类使用了 AccountDAO 和 TranferTransactionDAO,其中 transfer 方法负责整个转帐操作业务。TransferServiceImpl 负责所有的业务逻辑,验证是否超额提取并更新帐户余额。
一切并不复杂,对于这个例子来说,贫血模型工作得非常好!这是因为这个例子相当简单,业务逻辑也不复杂,一旦业务逻辑变得复杂,TransferServiceImpl 就会膨胀。
我有幸见过一个电商项目下单方法写了 3k 行的代码,最后被上司安排了对这部分代码开了分支,花了一个周的时间,针对性的做了一些各模块化方法优化。
总结
简单来说,就是 domain ojbect 包含了不依赖于持久化的领域逻辑,而那些依赖持久化的领域逻辑被分离到 Service 层。
优点:
1、各层单向依赖,结构清楚,易于实现和维护
2、设计简单易行,底层模型非常稳定
缺点:
1、domain object 的部分比较紧密依赖的持久化 domain logic 被分离到 Service 层,显得不够 OO
2、Service 层过于厚重
附言
本篇如有错误,请及时指出,马上修改。
非常非常重要的事情
本文首发于【黑壳博客】,文章持续更新,可以微信搜索【黑壳博客】点个关注 文章第一时间阅读。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于