前言:
从第一次系统地阅读《Java与模式》这本书已经过去一年多了,我一直没有有意的去使用设计模式,原因有两点:一是,对设计模式理解有些点不深入,使用起来比较吃力;二是,没有一个合适场景去使用设计模式。最近,我在负责一个新系统的对接钉钉流程中心,调用相关流程的开发工作,我在之前已经钉钉流程对接2次了,都这块业务理解比较准确和成熟。如果要是搬用原来的代码没有问题,但是原来的写法复用很差,每次新增一个流程,都有把原来的接口复制一遍,然后再对每一个方法中部分代码进行修改。基于以上背景,我觉得应该使用设计模式,对钉钉流程模块进行重新架构。
一、业务简单介绍:
对钉钉流程的业务进行抽象,其实也没有几个操作,主要操作如下:发起流程操作(首次发起、重新发起)、处理实例同意、处理实例拒绝、处理实例撤回、处理各个任务等,操作就是方法,接口里面就会有这几个方法。当然了某些流程可能会有些其它操作,即其它的方法。
Tips:实例:调用一次钉钉流程称为一个实例;任务:钉钉流程中有各个审批节点,每个节点称为一个任务。
二、设计模式的应用:
1.设计的思想:
通过以上对业务的简单介绍,最笨的实现方法就是写一个接口,接口中有这几个方法,然后实现接口中方法,下次来一个流程复制原来代码进行修改,然后以此类推,好一点程序员会对每个方法公用的部分抽象成私有方法,让每个方法调用。 其实这样很笨,代码复用很低,不方便维护。其实,这里可以使用策略模式,每个流程都有这几个操作,每种具体流程都是一种策略,即抽象成代码语言就是,有一个公用流程接口,每一种流程去实现这个接口;但是单独使用一个策略模式是不行的,因为每一种流程中的操作都有重复的子操作,例如,处理实例相关操作都会保存审批记录。这些操作中的重复子操作可以运用模板方法的设计模式,即顶层帮你实现公共通用的操作,你只需要基础该顶层抽象类。到现在,你会遇到一个问题,如何让策略模式的接口,可以使用上模板方法模式,这里就需要缺省适配器模式进行过度。等这些操作完成后,你会发现策略模式的调用很不方便,这里也可使用简单工厂方法模式,让工厂来创建具体的策略。
我在重新架构钉钉流程模块的时候,也使用Spring bean周期的中一些思想(可能其他方法中也有),Spring bean会提供初始化bean之前 操作的方法和初始化bean成功之后的操作方法,方便操作者对bean的修改。这个发起流程的操作有点想,调用流程中心的操作我会封装到底层,调用钉钉流程之前,需要一个方法封装钉钉相关组件;调用钉钉流程成功之后,需要一个方法去处理其他的业务操作。策略接口中也使用到了泛型,抽象方法中也使用了断言,这里就不展开介绍了。
学习设计模式,一定少不了UML图,下面的类图(使用visual-paradigm在线版工具画出的类图)就是对这次钉钉流程重新架构的描述,因为是内部系统, 不方便使用源代码,我这里就模拟了两个方法(发起流程和处理实例同意)。
2.相关代码:
(1)策略接口:
package com.hanxiaozhang.designpattern.strategy.service;
/**
* 〈一句话功能简述〉<br>
* 〈策略模式接口〉
*
* @author hanxinghua
* @create 2020/5/11
* @since 1.0.0
*/
public interface ProcessStrategy<T> {
/**
* 发起流程
*
* @param t
* @param againFlag 重新发起标识:true 重新发起,false 第一次发起
*/
void startProcess(T t, Boolean againFlag);
/**
* 处理实例同意
*
* @param instance
*/
void handleInstanceAgree(Object instance);
}
(2)策略抽象的缺省适配器类:
package com.hanxiaozhang.designpattern.strategy.service.impl;
import com.hanxiaozhang.common.dao.DictDao;
import com.hanxiaozhang.common.domain.DictDO;
import com.hanxiaozhang.designpattern.strategy.service.ProcessStrategy;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* 〈一句话功能简述〉<br>
* 〈抽象策略的缺省适配器〉
*
* @author hanxinghua
* @create 2020/5/11
* @since 1.0.0
*/
public abstract class AbstractProcessStrategyAdapter<T> implements ProcessStrategy<T> {
@Resource
private DictDao dictDao;
@Override
@Transactional(rollbackFor = Exception.class)
public void startProcess(T t,Boolean againFlag){
// 发起流程之前的操作
Object obj = this.startProcessOperateBefore(t,againFlag);
// 断言
assert obj == null:"[startProcessOperateBefore]方法返回值空,调用流程会报错!";
// 调用流程中心
System.out.println("模拟调用流程中心..."+obj.toString());
// 发起流程之后的操作
this.startProcessOperateAfter(t,againFlag);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void handleInstanceAgree(Object instance) {
// 模拟保存
this.saveAgreeApprovalLog(instance);
// 实例同意的操作
this.instanceAgreeOperate(instance);
}
/**
* 发起流程之前的操作
*
* @param t
* @param againFlag
* @return
*/
public abstract Object startProcessOperateBefore(T t,Boolean againFlag);
/**
* 发起流程成功之后的操作
*
* @param t
* @param againFlag
*/
public abstract void startProcessOperateAfter(T t,Boolean againFlag);
/**
* 实例同意的操作
*
* @param instance
*/
public abstract void instanceAgreeOperate(Object instance);
/**
* 保存实例同意的审批记录
*
* @param instance
*/
private void saveAgreeApprovalLog(Object instance){
DictDO dict = DictDO.builder().name("测试一下").build();
dictDao.save(dict);
// throw new RuntimeException("模拟一下异常");
}
}
(3)流程A策略类:
package com.hanxiaozhang.designpattern.strategy.service.impl;
import com.hanxiaozhang.designpattern.strategy.domain.A;
import org.springframework.stereotype.Service;
/**
* 〈一句话功能简述〉<br>
* 〈〉
* 建议强制指定一下bean的id
*
* @author hanxinghua
* @create 2020/5/11
* @since 1.0.0
*/
@Service("aProcess")
public class AProcess extends AbstractProcessStrategyAdapter<A> {
@Override
public Object startProcessOperateBefore(A a, Boolean againFlag) {
System.out.println("流程A发起流程前的准备...");
return new Object();
}
@Override
public void startProcessOperateAfter(A a, Boolean againFlag) {
System.out.println("流程A发起流程成功后的处理...");
}
@Override
public void instanceAgreeOperate(Object instance) {
System.out.println("流程A同意的处理...");
}
}
(4)流程B策略类:
package com.hanxiaozhang.designpattern.strategy.service.impl;
import com.hanxiaozhang.designpattern.strategy.domain.B;
import org.springframework.stereotype.Service;
/**
* 〈一句话功能简述〉<br>
* 〈〉
*
* @author hanxinghua
* @create 2020/5/11
* @since 1.0.0
*/
@Service("bProcess")
public class BProcess extends AbstractProcessStrategyAdapter<B> {
@Override
public Object startProcessOperateBefore(B b, Boolean againFlag) {
System.out.println("流程B发起流程前的准备...");
return new Object();
}
@Override
public void startProcessOperateAfter(B b, Boolean againFlag) {
System.out.println("流程B发起流程成功后的处理...");
}
@Override
public void instanceAgreeOperate(Object instance) {
System.out.println("流程B同意的处理...");
}
}
(5)策略工厂类:
package com.hanxiaozhang.designpattern.strategy.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 〈一句话功能简述〉<br>
* 〈策略工厂〉
*
* @author hanxinghua
* @create 2020/5/11
* @since 1.0.0
*/
@Component
public class ProcessStrategyFactory {
/**
* Spring会自动将Strategy接口的实现类注入到这个Map中,
* key为bean id,value值则为对应的策略实现类
*
*/
@Autowired
private Map<String, ProcessStrategy> strategyMap;
/**
* 通过名字获取策略实现类
*
* @param strategyName
* @return
*/
public ProcessStrategy getByName(String strategyName) {
return strategyMap.get(strategyName);
}
}
(6)调用测试类:
package com.hanxiaozhang.designpattern.strategy.controller;
import com.hanxiaozhang.designpattern.strategy.constant.ProcessTypeEnum;
import com.hanxiaozhang.designpattern.strategy.domain.A;
import com.hanxiaozhang.designpattern.strategy.domain.B;
import com.hanxiaozhang.designpattern.strategy.service.ProcessStrategyFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 〈一句话功能简述〉<br>
* 〈测试Controller〉
*
* @author hanxinghua
* @create 2020/5/11
* @since 1.0.0
*/
@Controller
@RequestMapping("/")
public class TestController {
@Autowired
private ProcessStrategyFactory strategyFactory;
@RequestMapping("")
@ResponseBody
public String test(){
strategyFactory.getByName(ProcessTypeEnum.A.getKey()).startProcess(new A(),false);
strategyFactory.getByName(ProcessTypeEnum.B.getKey()).startProcess(new B(),false);
return "OK";
}
}
(7)其他:
package com.hanxiaozhang.designpattern.strategy.constant;
/**
* 〈一句话功能简述〉<br>
* 〈Process流程枚举类〉
*
* @author hanxinghua
* @create 2020/5/14
* @since 1.0.0
*/
public enum ProcessTypeEnum {
A("aProcess","流程A"),
B("bProcess","流程B");
private String key;
private String name;
ProcessTypeEnum(String key, String name) {
this.key = key;
this.name = name;
}
public String getKey() {
return key;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "ProcessTypeEnum{" +
"key='" + key + '\'' +
", name='" + name + '\'' +
'}';
}
}
-------------------------------------------------------------
package com.hanxiaozhang.designpattern.strategy.domain;
/**
* 〈一句话功能简述〉<br>
* 〈流程A实例对象〉
*
* @author hanxinghua
* @create 2020/5/14
* @since 1.0.0
*/
public class A {
}
-------------------------------------------------------------
package com.hanxiaozhang.designpattern.strategy.domain;
/**
* 〈一句话功能简述〉<br>
* 〈流程B实例对象〉
*
* @author hanxinghua
* @create 2020/5/14
* @since 1.0.0
*/
public class B {
}
三、源码地址:
https://gitee.com/hanxinghua2017/springboot_demo.git 《 springboot-spring-knowledge模块-designpattern.strategy包》