系统目前支持三种业务流程,业务A, 业务B,业务C,每个流程有相同的业务逻辑,也包含很多的特性化业务。
由于之前业务流程的开发是快速迭代的,而且迭代了很多次,开发同学们应该可以理解,过程中免不了面向过程、CV大法…
现在无论是业务发展,还是我们产研团队对于业务的理解,都趋于稳定,是时候该还债了,重构代码,我辈义不容辞!
恰好,新需求来了,需要在原有业务B流程中增加一个业务环节,申请支付:业务发起时扣减对应客户的资产余额。
业务流程A抽象后伪码如下:
public void applyPay(Request request) {
//数据查询
query();
//数据校验
doCheck(request);
//业务逻辑处理
if (request.getFlag()) {
//资产校验
//扣减资产
//生成审批流数据
//推送钉钉
processA();
} else {
//资产校验
//扣减资产
//生成审批流数据
//推送钉钉
processB();
}
//数据更新
update();
}
更巧的是之前业务A中做过类似的业务,而且需求评估的时候产品信誓旦旦的说业务B、C中肯定不会有这个业务。
可以预见的是业务C中将来也会有这个业务。
Don not Repeat yourself,这个逻辑公共化处理是必要的。
对于上述业务流程,很容易想到模板模式对代码进行通用性抽象优化:
定义算法步骤骨架,并允许子类对一个或多个步骤进行实现
模板模式很容易理解。
public interface IApplyPayProcessor {
void applyPay(ApplyPayRequest request);
}
public abstract class AbstractApplyPayProcessor<T> implements IApplyPayProcessor{
/**
* 申请支付模板方法
* @param request
*/
@Override
public void applyPay(ApplyPayRequest request) {
//数据查询
T t= query(request);
//校验
check(t);
//业务逻辑处理
process(t, request);
//数据保存
save(t);
//通用业务逻辑处理
sendMsg(t);
}
private void sendMsg(T t) {
//发送业务逻辑完成通知 event、msg...
...实际代码省略
}
/**
* 根据业务,子类实现
* @param request
* @return
*/
public abstract T query(ApplyPayRequest request);
/**
* 根据业务,子类实现
* @param t
* @return
*/
public abstract Boolean check(T t);
/**
* 根据业务,子类实现
* @param t
* @param request
*/
public abstract void process(T t, ApplyPayRequest request);
/**
* 根据业务,子类实现
* @param t
*/
public abstract void save(T t);
}
对于业务A,继承AbstractApplyPayProcessor
,并根据自身业务的逻辑,实现相应的方法。
对于业务B、业务C也是类似的代码,只需要实现相应的方法,无需对业务流程进行重新的编排,毕竟父类已经定义了骨架方法。
public class BizAApplyPayProcessor extends AbstractApplyPayProcessor<BizADO>{
@Override
public BizADO query(ApplyPayRequest request) {
return new BizADO();
}
@Override
public Boolean check(BizADO o) {
//..子类业务逻辑省略
return Boolean.TRUE;
}
@Override
public void process(BizADO o, ApplyPayRequest request) {
//..子类业务逻辑省略
}
@Override
public void save(BizADO o) {
//..子类业务逻辑省略
}
}
经过第一步模板模式优化后,我们该怎么使用呢
通常我们会在service
层这样使用:
public void doApplyPay(ApplyPayRequest request) {
....
其他业务逻辑
if (request.getType().equals(A)) {
BizAApplyPayProcessor processor = new BizAApplyPayProcessor();
processor.applyPay(request);
} else if (request.getType().equals(B)) {
BizBApplyPayProcessor processor = new BizBApplyPayProcessor();
processor.applyPay(request);
} else if (request.getType().equals(C)) {
BizCApplyPayProcessor processor = new BizCApplyPayProcessor();
processor.applyPay(request);
}
..... 其他业务逻辑
}
我们很大概率会按照上述代码的编写方式,先通过if/else
判断,然后使用new
这个关键字,实例化对应的实体,或者Spring
的方式注入对应的方式。
new
代表着实例化,意味着我们的代码需要依赖具体的实现,而通常推荐的编程是面向接口
而不是面向实现。
当有新的类型,比如业务D被添加进来时,这个service
需要修改,if/else
逻辑需要改变,不符合开闭原则
,设计模式的核心的一点就是封装可变的部分。
可以使用工厂模式
将if/else
封装在单独的类,伪代码如下:
public class BizFactory {
public static IApplyPayProcessor getApplyPayProcessor(String bizType) {
IApplyPayProcessor processor;
if (A) {
processor = new BizAApplyPayProcessor();
processor.applyPay(request);
} else if (B) {
processor = new BizBApplyPayProcessor();
processor.applyPay(request);
} else if (C) {
processor = new BizCApplyPayProcessor();
processor.applyPay(request);
}
return processor;
}
}
//service层使用工厂类直接获取对应的实例
public class ClientService {
public void doApplyPay(ApplyPayRequest request) {
BizFactory.getApplyPayProcessor(request.getType());
}
}
这样做起来看起来只是将代码从service
层移动到了一个BizFactory
中,问题依然是存在的,有什么好处呢?
service
层不再随着实现的变化而变化,尽量往单一职责去靠拢,注意我这里说的是尽量,因为往往我们会做一个取舍。BizFactory
可以有很多业务工厂,其他业务service
以后也不需要变化,只需要变化BizFactory
我们可以看到对于工厂本身而言,虽然我们将对象的使用和创建已经分离开来,使用工厂模式来专注于对象的创建,当新增业务类型时,比如业务D时,我们需要修改
工厂模式,不符合开闭原则。
如何进行进一步优化呢,让工厂模式不随着业务类型的增加而进行修改呢。
答案即是使用策略模式
定义一系列的算法或策略,并将每个算法封装在
单独的类
中,使得它们可以互相替换
。通过使用策略模式,可以在运行时根据需要选择
不同的算法,而不需要修改
客户端代码。
step1 先定一个枚举BizTypeEnum
用来标识业务类型,也就是策略的集合。
@Getter
public enum BizTypeEnum {
A(1, "业务A"),
B(2, "业务B"),
C(3, "业务C"),
D(4, "业务D"),
;
private int code;
private String msg;
BizTypeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
/**
* 整形值转换为枚举类
*
* @param value 值
* @return 枚举类
*/
public static BizTypeEnum valueOf(int value) {
for (BizTypeEnum anEnum : values()) {
if (value == anEnum.getCode()) {
return anEnum;
}
}
return null;
}
}
step2 定义一个注解,ApplyPay
用来标记策略类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplyPay {
BizTypeEnum bizType() default BizTypeEnum.A;
}
step3 使用注解@ApplyPay
,改造标记策略类,
@ApplyPay(bizType = BizTypeEnum.A)
public class BizAApplyPayProcessor extends AbstractApplyPayProcessor<BizADO>{
@Override
public BizADO query(ApplyPayRequest request) {
return new BizADO();
}
@Override
public Boolean check(BizADO o) {
//..子类业务逻辑省略
return Boolean.TRUE;
}
@Override
public void process(BizADO o, ApplyPayRequest request) {
//..子类业务逻辑省略
}
@Override
public void save(BizADO o) {
//..子类业务逻辑省略
}
}
@ApplyPay(bizType = BizTypeEnum.B)
public class BizBApplyPayProcessor extends AbstractApplyPayProcessor<BiZBDO>{
@Override
public BiZBDO query(ApplyPayRequest request) {
return null;
}
@Override
public Boolean check(BiZBDO biZBDO) {
return null;
}
@Override
public void process(BiZBDO biZBDO, ApplyPayRequest request) {
}
@Override
public void save(BiZBDO biZBDO) {
}
}
step4 识别加载封装策略类,改造工厂类
可以借助Spring的拓展能力,识别策略类的注解,并将策略实例化后,使用集合保存,当然如果没有使用Spring框架,也可以借助反射等来进行此动作。
public class BizFactory implements ApplicationListener<ContextRefreshedEvent> {
private static Map<BizTypeEnum, IApplyPayProcessor> APPLY_PAY_MAP = new ConcurrentHashMap<>(8);
public static IApplyPayProcessor getApplyPayProcessor(BizTypeEnum bizType) {
return APPLY_PAY_MAP.get(bizType);
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
//申请支付工厂初始化
Map<String, Object> beansWithAnno = applicationContext.getBeansWithAnnotation(ApplyPay.class);
if (beansWithAnno != null) {
beansWithAnno.forEach((key, value) -> {
BizTypeEnum bizType = value.getClass().getAnnotation(ApplyPay.class).bizType();
APPLY_PAY_MAP.put(bizType, (IApplyPayProcessor) value);
});
}
}
}
使用策略模式改造完之后,显而易见的改造后,不同的业务被封装在不同的类中,工厂模式也无需知晓每个类,只需要根据业务类型加载即可。
每次新增业务类型时,只需要新增策略类即可,无需对service
和factory
进行修改。