利用简单工厂、策略模式、适配器模式对系统的一次可扩展性的改进

钉钉审批中回调事件引发的可扩展性方案

先前环境

其实对环境并没有要求,但是不说自己的环境就是在耍流氓

  • Java: "1.8.0_261"
  • SpringBoot:2.0.4.RELEASE
  • Maven:3.6.3
  • Idea:2020.2
  • Window10

解决方案使用到了简单工厂策略模式适配器模式

一、问题的发现

问题描述

门店交接班实收款项修改的任务中,需要用到钉钉的回调接口,但是公司已经将钉钉的回调已经封装的还可以了,重点:只需要把自己的业务逻辑的处理方法写到他的代码中就可以了,这里有个不大不小的问题,就是对原本代码的修改,每次有新的业务的时候都要去修改这一部分的代码,而且业务一直在发展,钉钉审批的功能也会被用的越来越多,导致很有可能有多个人同时修改这一部分代码(虽然改同一份代码没法避免,但是我们可以把对这个类的压力调整到另一个类上面去),冲突要修改的代价也会越来越大

/**
 * **.**.management.api.dingtalk.service.impl.ApproveResultProcessServiceImpl#processFlow
 * @author codeDorado
 */
public class ApproveResultProcessServiceImpl implements ApproveResultProcessService {
    @Resource
    private BasalDiscService basalDiscService;
    ...
    /**
     * 回调具体的每个具体逻辑
     *
     * @param info 审批实例id
     * @param json 回调数据
     */
    @Override
    public void processFlow(DingTalkInstanceInfo info, String json) {
        // 基盘业务处理
        if (InstanceTypeEnum.BASAL_DISC_TYPE.getCode().equals(info.getInstanceType())) {
            basalDiscService.callbackProcess(json);
        }
        // 调价单业务处理
        if (InstanceTypeEnum.ADJUST_BILL_TYPE.getCode().equals(info.getInstanceType())) {
            adjustSheetService.callbackPriceBill(json);
        }
        // 新加的业务逻辑都在这个地方
        ...
    }
}

看到这份代码的时候就想到了策略模式状态模式,毕竟 if 实在是太多了,但是当时想怎么解决的时候只想到怎么用一种设计模式去优化,发现还是无法避免对 InstanceType(审批业务类型) 的判断。

第二天上班的时候想到既然无法避免判断,那就把判断转移到另一个类中去,让这份老代码不再 "伤筋动骨",所以我想到了用简单工厂进行转移,但是需要返回一个统一的接口,这个很好办嘛,让这些接口都去实现一个统一的接口,实现同一个方法不就好了嘛,说干就干。

二、解决步骤

1、抽离一个公共的接口

抽离这个接口去定义具体调用类去使用哪个方法

package cn.**.management.api.dingtalk.ext;
/**
 * @author: codedorado
 * @Date: 2020/10/19 10:55
 * @version: 1.0
 **/
public interface DingTalkCallBackHandleService {

    /**
     * 钉钉回调本地业务处理接口
     *
     * @param json 钉钉回调信息
     */
    void callBackHandle(String json);

}

2、建立工厂类

  • 这里使用了 Spring 的一个特性,将同一个接口的实现类都注入到了 factory 的 Map中去,key 对应着自己定义的名字。
  • 因为系统中的业务逻辑是用数字表示的,所以需要建立一个 map,存储着业务类型与具体处理类之间的关系。在项目刚启动的时候就由 Spring 的初始化事件将对应关系添加到对应 map 中去
@Component
public class DingTalkCallBackFactory {

    @Resource
    Map<String, DingTalkCallBackHandleService> factory;

    /**
     * 存放业务类型与类名之间的关系
     */
    private static final Map<Integer, String> BUSINESS_CODE = new HashMap<>(16);

    @PostConstruct
    public void initFactory() {
        log.info("钉钉回调业务处理关系回调开始初始化");
        
        BUSINESS_CODE.put(1, "BasalDiscCallBackService");
        BUSINESS_CODE.put(2, "AdjustSheetCallBackService");
        BUSINESS_CODE.put(3, "ProductApproveCallBackService");
        BUSINESS_CODE.put(4, "StockSheetCallBackService");
        BUSINESS_CODE.put(5, "StockSheetCallBackService");
        BUSINESS_CODE.put(5, "CashierRecordCallBackService");

        log.info("钉钉回调业务处理关系回调初始化完毕");
    }

    /**
     * 得到具体的处理者
     *
     * @param type 业务类型
     * @return Processor
     */
    public DingTalkCallBackHandleService getCallBackProcessor(Integer type) {
        if (Objects.isNull(type)) {
            throw new NullPointerException("处理类型不能为null");
        }
        return factory.get(BUSINESS_CODE.get(type));
    }
}

3、建立适配器类

  • 这里的适配器的使用是因为老的代码与方法名存在不同地方使用的情况,这样就导致如果我要修改不同人的代码,可能会导致意想不到的错误,所以给老的代码加一个适配器不失为一个好的方法,缺点就是系统的类变多了。
  • 这里用到的是对象适配器模式,适配器类去实现自己定义的接口,重写的方法里面使用自己具体的逻辑。
/**
 * @author: codeDorado
 * @Date: 2020/10/20 17:35
 * @version: 1.0
 **/
@Service("CashierRecordCallBackService")
public class CashierRecordCallBackServiceImpl implements DingTalkCallBackHandleService {

    @Resource
    private CashierRecordDingTalkCallBackService cashierRecordDingTalkCallBackService;

    @Override
    public void callBackHandle(String json) {
        // 门店修改实收款项逻辑
        cashierRecordDingTalkCallBackService.callbackModifyCollection(json);
    }
}

4、重写老代码

  • 修改成这样以后这个类就被我们封装成不可变类了,将可变的内容提取出来,也有利于系统的可靠性,将冲突的地方转移到了其他的地方去处理。
@Service
@Slf4j
public class ApproveResultProcessServiceImpl implements ApproveResultProcessService {

    @Resource
    private DingTalkCallBackFactory dingTalkCallBackFactory;

    /**
     * 回调具体的每个具体逻辑
     *
     * @param info 审批实例id
     * @param json 回调数据
     */
    @Override
    public void processFlow(DingTalkInstanceInfo info, String json) {
        // 根据业务类型生成真正要执行的对象,执行对应逻辑
        DingTalkCallBackHandleService callBackProcessor = dingTalkCallBackFactory.getCallBackProcessor(info.getInstanceType());
        callBackProcessor.callBackHandle(json);
    }
}

三、后续

  • 以上就是从问题的发现到具体解决的方案,虽然改的只是一个小的地方,但是这个小的地方在很多业务里面都有被用到了,后续也需要扩展,所以提取出来的话也算是小的优化?而且这种情况不止在自己的系统中出现过,同学所在的公司也有类似的问题,但是因为实在是太老了,也并没有继续扩展的想法,所以也就暂时没必要去解决这个问题了。解决方案应该不是最优的,还需要进一步观察以及优化,但是这种方案是介于简单与复杂中间的一种。
  • 在实现这些逻辑的时候也出现过一些问题,比如在使用 Spring 对这一类接口的实现进行注入的时候,一开始并没有使用这种方式,还是在舍友讨论的时候说到的一篇博客才让我拨云见日,地址如下 简单工厂+策略模式在工作中的运用

四、暂时牵扯到的其他逻辑

定时任务中查询钉钉审批状态中用到的代码跟审批回调的代码逻辑一致,代码相同,因为分了两个模块,所以这个地方也出现了这种问题。

五、额外使用到东西

1、状态枚举类

public enum InstanceTypeEnum {
    /**
     * 基盘业务-审批ID
     */
    BASAL_DISC_TYPE(0),
    /**
     * 调价单业务-审批ID
     */
    ADJUST_BILL_TYPE(1),
    ...;

    private final Integer code;

    InstanceTypeEnum(Integer code) {
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }
}

2、目录结构

目录结构

posted @ 2020-10-23 09:51  atomFix  阅读(232)  评论(1编辑  收藏  举报