(接上文《软件设计不是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;// =======// 这里有一个构造方法,为了节约篇幅省去// =======@Overridepublic void doCommand() {if(this.settlementStrategy.needValidate(settlement)) {this.settlementStrategy.validate(settlement);}}@Overridepublic void redo() {// 该验证命令在整个控制逻辑出现问题时,不用做对应的错误补偿}
}
- 对结算单信息进行信息转换的命令
// 对结算单信息进行信息转换的命令
public class BalanceSettlementCommand implements SettlementCommand {private SettlementStrategy<Settlement> settlementStrategy;private BalanceStrategy balanceStrategy;private Settlement settlement;// =======// 这里有一个构造方法,为了节约篇幅省去// =======@Overridepublic void doCommand() {this.settlementStrategy.balance(settlement, balanceStrategy);}@Overridepublic 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;}@Overridepublic void doCommand() {if(CollectionUtils.isEmpty(this.settlementEventListeners)) {return;}// 对上层模块进行事件通知for (SettlementEventListener settlementEventListener : settlementEventListeners) {try {settlementEventListener.onBalanced(settlement);} catch(RuntimeException e) {log.error(e.getMessage() , e);}}}@Overridepublic void redo() {