(接上文《软件设计不是CRUD(15):低耦合模块设计理论——行为抽象与设计模式(中)》)
3.2.4、之前的业务逻辑需要关注后续逻辑的执行成败,并调整自身执行的情况
这个场景在之前场景的基础上增加了新的控制要求,具体来说就是之前已经完成的控制逻辑执行,需要在后续控制逻辑执行出现问题时,获得一种错误补偿的方式。这和我们常说的数据库事务还有所区别(当然数据库事务回滚也是必要的一种错误补偿方式),因为错误补偿不一定是数据库事务回滚。
这实际上也是在之前介绍的单个业务控制点的基础上,对一个业务维度的多个实现进行错误补偿控制方式的一种扩充。只不过这里需要解决的是一个控制逻辑上,多个业务控制点之间如何协进行调错误补偿的问题。这里推荐一种命令模式的设计方式,如下图所示:
为什么这里需要将具体结算单的策略传入到命令中,这是因为不可能为某一种具体的结算单创建一种对应的命令,例如不可能为SettlementA这种类型的结算单创建专门的一种转换命令,然后再为SettlementB这种类型的结算单创建专门的一种转换命令,否则可能会导致类爆炸(类爆炸的概念在前文中介绍过,这里不再赘述,导致类爆炸的原因主要是因为维度合并,且设计无法控制维度扩展)。
- 这是命令接口的定义
// 结算单执行命令
public interface SettlementCommand {
// 命令执行方法
public void doCommand();
// 用于在控制逻辑出现错误的情况下
// 要求具体命令的业务执行过程进行错误补偿
public void redo(Throwable e);
}
- 对结算单信息进行验证的命令
// 对结算单信息进行验证的命令
public class ValidateSettlementCommand implements SettlementCommand {
private SettlementStrategy<Settlement> settlementStrategy;
private Settlement settlement;
// =======
// 这里有一个构造方法,为了节约篇幅省去
// =======
@Override
public void doCommand() {
if(this.settlementStrategy.needValidate(settlement)) {
this.settlementStrategy.validate(settlement);
}
}
@Override
public void redo() {
// 该验证命令在整个控制逻辑出现问题时,不用做对应的错误补偿
}
}
- 对结算单信息进行信息转换的命令
// 对结算单信息进行信息转换的命令
public class BalanceSettlementCommand implements SettlementCommand {
private SettlementStrategy<Settlement> settlementStrategy;
private BalanceStrategy balanceStrategy;
private Settlement settlement;
// =======
// 这里有一个构造方法,为了节约篇幅省去
// =======
@Override
public void doCommand() {
this.settlementStrategy.balance(settlement, balanceStrategy);
}
@Override
public void redo() {
// 当整个控制逻辑发生错误时,该命令需求重置已设定的结算费用,并清理数据库中的设定信息
}
}
- 发送事件通知的命令
// 发送事件通知的命令
@Slf4j
public class SendEventSettlementCommand implements SettlementCommand {
private Settlement settlement;
@Autowired(required = false)
private List<SettlementEventListener> settlementEventListeners;
public SendEventSettlementCommand(Settlement settlement) {
this.settlement = settlement;
}
@Override
public void doCommand() {
if(CollectionUtils.isEmpty(this.settlementEventListeners)) {
return;
}
// 对上层模块进行事件通知
for (SettlementEventListener settlementEventListener : settlementEventListeners) {
try {
settlementEventListener.onBalanced(settlement);
} catch(RuntimeException e) {
log.error(e.getMessage() , e);
}
}
}
@Override
public void redo() {