责任链模式的运用

需求描述:

被请求系统需要按照请求系统发送的有序集合内容进行有顺序的报表运算

其中有三种以上的不同执行逻辑,通过集合中实体的某个字段来做判断,并执行对应逻辑,同时,需考虑执行性能, 同一执行顺序需要做并行处理,如图:
image-20240530155931847

与此同时,还有对应的初始化处理、执行完成回调等。

技术设计:

  • 因为执行逻辑会有新增的情况,因此直接编码的方式并不可取,这只会让代码中的if-else越来越多,导致代码可读性、拓展性等都大大降低。
  • 运用责任链设计模式,将不同逻辑封装到不同类中(他们的共同特点是都继承了一个公有的抽象类,其为责任链模式的核心类)。
  • 在外部将责任链根据执行集合进行归类并且按顺序组装。
  • 触发责任链执行逻辑。
  • 当新增不同逻辑时,只需要新增一对应执行逻辑的类,并改动动态组装的代码逻辑即可完成。

逻辑增强(业务逻辑逐渐复杂时考虑):

当执行逻辑所涉及的类数量增加到一定量级时,动态组装的代码逻辑将会变得比较复杂,真到这种情况时,可以采用 工厂模式或者 建造者模式将对应标识应该创建什么类型的逻辑类的代码放入到对应设计模式中,以解决因逻辑类增多造成的动态组装代码复杂的问题。

实现:

代码结构:

.
├── AbstractProcessLink.java  #抽象父类
├── ProcessInfo.java  # 责任链中封装实体
├── StartProcessLink.java  # 初始化类
├── dto
│   ├── CptDelDTO.java
│   └── CptProcessVO.java
├── finereport
│   ├── CptExecutor.java  # 逻辑执行线程池
│   ├── CptProcessLink.java  # 逻辑执行类
│   └── ProvinceCptProcessLink.java  # 逻辑执行类
└── workflow
    └── WorkflowProcessLink.java  # 逻辑执行类

责任链父类实现:

import lombok.Data;

/**
 * 责任链抽象父类
 * 定义了当前执行逻辑抽象方法、下一节点执行逻辑
 */
@Data
public abstract class AbstractProcessLink {

    protected ProcessInfo processInfo;

    protected boolean errorFlag = false;

    private AbstractProcessLink next;

    public AbstractProcessLink next() {
        return next;
    }

    public void appendNext(AbstractProcessLink next) {
        this.next = next;
    }

    public abstract String process();

    @Override
    public String toString() {
        return "AbstractProcessLink{" +
                "processInfo=" + processInfo +
                ", errorFlag=" + errorFlag +
                ", next=" + next +
                '}';
    }
}
  • 责任链模式核心类,封装了责任链所需参数、下一节点以及抽象方法,用于具体逻辑实现类继承后重写该方法完成逻辑区分。

责任链组装:

    /**
     * 构建执行链
     *
     * @param fLowRequest 请求
     * @return 执行链
     */
    private AbstractProcessLink getProcessLink(CalcDTO fLowRequest) {
        List<CalcBatchSteps> pmaCoreCalcBatchSteps = fLowRequest.getCalcSteps();
      	// list -> map 并排序
        LinkedHashMap<Long, ArrayList<CalcBatchSteps>> seqMap = sortStepsBySeq(calcBatchSteps);

        // 新增开始节点
        AbstractProcessLink abstractProcessLink = new StartProcessLink(coreService, callbackHelper,
                stepsRepository, shortCompanySingleton);
        abstractProcessLink.setProcessInfo(ProcessInfo.builder()
                .periodName(fLowRequest.getPeriodCode())
                .serialNum(fLowRequest.getSerialNum())
                .reportType(fLowRequest.getReportType())
                .build());

        // 遍历map
        seqMap.forEach((seq, list) -> {
            List<CalcBatchSteps> defaultList =
                    list.stream().filter(e -> !Constants.PROVINCE_ONCE_FLAG.equals(e.getOnceFlag())).collect(Collectors.toList());
            List<CalcBatchSteps> provinceList =
                    list.stream().filter(e -> Constants.PROVINCE_ONCE_FLAG.equals(e.getOnceFlag())).collect(Collectors.toList());
						// 不同类型构建不同责任链类
            if (CollectionUtils.isNotEmpty(defaultList)) {
                ProcessInfo processInfo = ProcessInfo.builder()
                        .seq(seq)
                        .serialNum(fLowRequest.getSerialNum())
                        .periodName(fLowRequest.getPeriodCode())
                        .reportType(fLowRequest.getReportType())
                        .stepList(defaultList)
                        .build();
              	// 将当前逻辑类组装进责任链中
                assembleLink(abstractProcessLink, processInfo, true);
            }

            if (CollectionUtils.isNotEmpty(provinceList)) {
                ProcessInfo processInfo = ProcessInfo.builder()
                        .seq(seq)
                        .serialNum(fLowRequest.getSerialNum())
                        .periodName(fLowRequest.getPeriodCode())
                        .reportType(fLowRequest.getReportType())
                        .stepList(provinceList)
                        .build();
                assembleLink(abstractProcessLink, processInfo, false);
            }
        });
        return abstractProcessLink;
    }
  • 在上述提到的逻辑增强即改造当前代码为 建造者工厂模式
  • 此处通过便利Map,获取的Map值List集合为可以并行的部分,到具体执行类中,会以统一逻辑对其进行并发执行。

责任链执行:

责任链执行相关类较多,去其中一个执行方法:

    @Override
    public String process() {

        // 数据初始化
        List<CalcBatchSteps> stepList = processInfo.getStepList();
        init(stepList);

        // 过滤省份公司
        List<MappingDTO> noProvinceCompanyLovInfo =
                companyLovInfo.stream().filter(e -> !PROVINCE_COMPANY_CODE.equals(e.getValue())).collect(Collectors.toList());

        // 批量执行
        List<CompletableFuture<Void>> tasks = new ArrayList<>();

        for (CalcBatchSteps step : stepList) {
            // 先执行省
            log.info("ProvinceCptProcessLink ==> first execute province!");
            LovValueDTO provinceLovValue = new LovValueDTO();
            provinceLovValue.setValue(PROVINCE_COMPANY_CODE);
            provinceLovValue.setMeaning(PROVINCE_COMPANY_NAME);
            doTask(provinceLovValue, step);

            // 后执行后面地市(可并行,多线程执行)
            log.info("ProvinceCptProcessLink ==> then execute province!");
            for (LovValueDTO lovValueDTO : noProvinceCompanyLovInfo) {
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> doTask(lovValueDTO, step),
                        CptExecutor::executeTask);
                tasks.add(future);
            }
        }
				// 错误截段或者执行下一节点
        executorAndNext(tasks);

        return null;
    }
  • 该类为 ProvinceCptProcessLink.java,当前 process(); 方法继承自抽象父类。通过实现这一方法达到不同类取不同逻辑的效果。
  • 在具体实现逻辑中会记录错误信息,并存入到类中,最后是否执行下一节点将会根据是否报错、等进行,同时,若是报错,将会提前回调。