接上文《软件设计不是CRUD(14):低耦合模块设计理论——行为抽象与设计模式(上)》
3.2、行为抽象中常见的控制逻辑形式
上文我们讨论了在功能的整个控制逻辑中,针对一个业务控制点上的控制方式,可以通过何种行为抽象的方式找到对应的设计模式并最终将需求转换为具有较强业务屈服度、较低耦合性的设计落地的方式。本节内容我们主要讨论整个控制逻辑串联多个业务控制点的行为抽象设计落地方式,也就是整个控制逻辑的行为抽象落地方式。为了便于讨论能够由浅入深,能够对问题具象化,本节我们还是借鉴上一节的讨论方式:既是列出归纳后的可能场景,并从最简单的场景开始进行介绍。
3.2.1、按照一个固定的工作顺序(无分支),将多个业务控制点串联起来,各控制点的工作逻辑可能相关但不相互影响
这里说的工作逻辑相关但不相互影响是什么意思呢?就是说后面的业务逻辑可能会基于前面的业务逻辑的工作结果继续工作,但是整个控制逻辑只有一个固定的工作顺序,所以不存在因为前面的业务逻辑由于不同的工作结果,而导致需要判定后续的业务逻辑是否能够执行的问题。所以本小节讨论的这种场景是最简单的一种控制逻辑工作场景,其中涉及的多个业务控制点没有因果关系,也不需要知晓各自的工作结果,也不会因为某一个业务控制点的执行结果而导致控制逻辑的工作顺序发生变化。
例如,结算单模块中的结算单计算功能,需要按照以下控制逻辑进行顺序调用
- 根据结算单类型的不同将传输的JSON结构的数据转换成不同格式的结算单对象。这是因为不同类型的结算单,结算单的数据解构不一样。
- 根据结算单类型的不同,调用不同结算单验证逻辑完成校验。如果验证没有通过则只需要告知调用者错误信息,并终止处理过程即可。
- 按照结算单计费方式的不同,调用不同的费用计算逻辑,并完成费用计算(注意,可能原始需求还会要求结算单计费方式如果有多个,则依次进行调用)。但无论什么样的计费方式,都是将结算的工作逻辑都是根据结算单上的单价,结合服务时长进行计算。但是要注意的是,不同结算单类型,获取单价、服务时长的方式可能是不一样的。
- 完成结算工作后,需要将结算事件通知出去,以便上层模块关注结算事件的功能,能够被驱动工作。
通过阅读以上工作过程的需求后,我们可以快速分析出需求中的每一个关键过程点涉及那些业务维度:
-
在结算单对象生成(具体代码落地时,就是JSON信息转换)时,结算单类型是关键的业务维度,不同的结算单类型结构不一样,生成逻辑也不一样。诸如A类型的结算单可能携带了两种明细信息以及至少20个必填的基本信息,但是B类型的结算单可能只携带了一种明细信息以及最多5个必须填写的基本信息。所以“结算单类型”是控制逻辑中第一个业务控制点上的关键业务维度。
-
结算单类型同样是结算单验证时的关键业务维度,也就是说不同类型的结算单,其基本信息校验过程也不一样。
-
而结算方式是结算单进行费用计算时的关键业务维度。但很明显的一个问题是,由于不同类型的结算单取得计算时所需单价、时长的方式是不一样的,所以实际上这个控制点会受到两个业务维度相交后的联合干预。
-
最后,控制逻辑需要在处理完成后将结算单数据发生变化的事件通知出去,这是一个典型的事件通知需求,这里为了简便实现我们暂时不考虑事件涟漪的问题。
经过以上对于业务维度和业务模型的分析,我们得到了以下关键信息:
业务控制点(控制顺序) | 涉及的业务维度 | 解决方式 | 其它备注说明 |
---|---|---|---|
转换不同的结算单信息 | 单个业务维度:结算单类型 | 一个简单的策略模式 | |
对不同的结算单信息进行边界校验 | 单个业务维度:结算单类型 | 一个简单的策略模式 | 注意:由于前两个业务控制点都只涉及同一个业务维度,所以可以放在一个设计中解决问题 |
对结算单进行不同的结算形成计算结果 | 两个交叉的业务维度:结算单类型 + 就是那单结算方式 | 使用一个桥接的方式,将两个可变的业务维度结合起来 | |
最后将数据变化的事件通知出去 | 事件通知,不涉及业务维度 | 不用考虑事件涟漪,所以只需要一个简单的事件通知方式 |
注意,控制逻辑中不应该有任何业务处理过程,控制逻辑中不应该有任何业务处理过程,控制逻辑中不应该有任何业务处理过程!(重要的事情说三遍)
有了以上的关键信息后,设计人员就可以进行行为抽象的设计落地了,如下所示:
- 首先定义抽象模型——只要实现了该抽象模型的具体模型都认为是“一种结算单”。
// 结算单抽象模型
public interface Settlement {
// 结算单类型必须是唯一的
public String type();
}
- 为了保证演示的准确性,这里我们假设有两种不同的结算单(A、B),这两种结算单的基本信息、明细信息都有所区别:
// 这是一种类型的结算单
@Getter
@Setter
public class SettlementA implements Settlement {
private final static String TYPE = "SettlementA";
// 中文名称
private String name;
// 结算单业务编号
private String code;
// 结算单类型
private String type = TYPE;
// 结算单总费用
private BigDecimal totalPrice;
// 结算单应结算费用
private BigDecimal payablePrice;
// 该结算单使用的结算方式类型
private String balanceType;
// ======== 该结算单其它特有的业务属性
@Override
public String type() {
return TYPE;
}
@Override
public String balanceType() {
return balanceType;
}
}
// 这是另一种类型的结算单
@Getter
@Setter
public class SettlementB implements Settlement {
private final