业务开发时,我们经常会面对很多复杂且长的流程,整个流程写下来可能,这个流程可能是一次执行,也可能是分节点,节点需要每次手动触发,那么我们的代码会很复杂,而且对于新手也很难掌握。同时,业务需求可能经常发生变更,又或者我们需要创建同样类似的流程,不得不对原代码频繁修改,经过几个迭代,可能没人愿意面对这堆代码。
那么是否有一种方法可以将复杂流程解耦,做成可配置化、可复用的通用流程呢,本文将提供一种解决方案,当然仁者见仁,智者见智,方法好不好很难说,但是总能学到一些东西。
本方案采用Aviator和策略模式实现,在这里我们只需要记住Aviator是一个表达式解析计算工具,可以实现动态表达式的计算,比如N+1、m == 1 && n == 2等。策略模式这里也不做详细了解,可先百度了解一下。
策略模式我们这里采用的是注解的方法实现。
策略注解类
/** * 策略注解 * * @author weitao * @date 2018/12/18 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface STStrategyAnnotation { /** * 策略类型 * * @return */ String type(); /** * 操作描述 * * @return */ String description(); }
这里的type使用的是String,主要是考虑通用性,另外可以很方便的支持多流程节点公用一个策略实现。
策略工厂类
/** * 策略工厂类 * * @author weitao * @date 2018/12/18 */ @Slf4j public class STContextStrategyFactory { public <O extends STIContext, T extends STIContextStrategy<O>> STIContextStrategy<O> doStrategy(String type, Class<T> clazz) { Map<String, T> beanMap = STSpringBeanUtils.getBeanMap(clazz); if (MapUtils.isEmpty(beanMap)) { log.error("获取class:{} 为空", clazz.getName()); } try { for (Map.Entry<String, T> entry : beanMap.entrySet()) { Object real = STAopTargetUtils.getTarget(entry.getValue()); STStrategyAnnotation annotation = real.getClass().getAnnotation(STStrategyAnnotation.class); List<String> keySet = Splitter.on("-").omitEmptyStrings().trimResults().splitToList(annotation.type()); if (keySet.contains(type)) { return entry.getValue(); } } } catch (Exception e) { log.error("获取目标代理对象失败:{}", e); } log.error("strategy type = {} handle is null", type); return null; } }
STContextStrategyFactory我们也可以定义成为一个utils类,不一定是依赖注入。
doStrategy有两个入参,type和Class。为什么要加class这个入参呢,主要是因为策略的实现方式很隐蔽,比如多个业务都使用了策略模式,那么我们很难区分哪些type是哪个业务的,所以我们会定义不同的业务接口作为入参,即可规范业务范围,也方便代码的定位。这个接口必须继承一个接口类,这个接口类就是我们的策略实现的接口类,如下:
/** * 策略基础接口类 每个策略业务接口类必须继承此类 * * @author weitao * @date 2018/12/18 */ public interface STIContextStrategy<T extends STIContext> { /** * 处理策略 * * @param t * @return STResultInfo */ STResultInfo<?> handleSeniority(T t); }
比如,我们有一个医生操作的策略业务,那么我们可以定义一个业务策略接口,如下:
/** * 操作策略 * * @author weitao * @date 2018/12/18 */ public interface DoctorOptStrategy extends STIContextStrategy<DoctorDTO> { }
那么我们所有的医生操作类的策略方式都需要实现这个接口,同样,我们可以定义无限多个业务策略接口。调用方法如下:
this.stContextStrategyFactory.doStrategy(String.valueOf(DoctorProcessDTO.OptType.AUDIT.getCode()), DoctorOptStrategy.class).handleSeniority(doctor);
策略实现类上的注解如下
@STStrategyAnnotation(type = "4101-4102-4103-4104", description = "处理医生信息的策略")
上面就是策略类的实现,总体来说就是通过接口类找到对应所有的实现,然后再根据类上的注解找到可以对应的实现类并执行。
接下来说说重点,如何使用Aviator,现在我们有一个业务场景:
- 医生的身份满足1,并且医生来源不等于3,走逻辑A
- 医生身份满足3-5,走逻辑B
- 医生身份满足6,走逻辑C
- 医生身份满足1,且来源等于3,走逻辑D
- 医生身份满足4,且医院是自营,走逻辑F
对于传统做法,我们肯定是一大堆的逻辑判断,if else无限嵌套,但是我们使用Aviator来做就很简单了,首先根据业务把逻辑代码化:
- 医生的身份满足1,并且医生来源不等于3,走逻辑A(identityType == 1 && createSource != 3)
- 医生身份满足3-5,走逻辑B( identityType >= 1 && identityType <= 5 && !isSelf)
- 医生身份满足6,走逻辑C ( identityType == 6)
- 医生身份满足1,且来源等于3,走逻辑D (identityType == 1 && createSource == 3)
- 医生身份满足4,且医院是自营,走逻辑F ( identityType == 4 && isSelf)
然后定义一个枚举(简单实现,复杂业务可入库做成动态配置),枚举实例就不列出来了,两个属性,一个是type,一个是expression。然后我们就可以通过 Aviator 来计算获取正确的枚举。
/** * 查询流程list * * @param doctorProcessDTO * @return */ public static Integer getType(DoctorProcessDTO doctorProcessDTO) { Map<String, Object> map = BeanMap.create(doctorProcessDTO); for (DoctorProcessEnum doctorProcessEnum : DoctorProcessEnum.values()) { if ((Boolean) AviatorEvaluator.execute(doctorProcessEnum.getExpression(), map)) { // 返回对应流程list return doctorProcessEnum.type; } } return null; }
拿到对应的枚举了,再用策略方式来执行对应的策略类。是不是很简单?
如果我们想的再深入一点,把枚举入库,那是不是可以实现流程的动态修改和动态新增?如果在已有现实的基础上,我们新增的其他的业务类型,比如:医生身份满足5,且是自营,是不是只需要改改配置,增加一条纪录就可以实现了呢?
如果我们想的再深入一点,我们面对的不是简单的业务拆分,是一串流程节点的流程,每个流程根据不同的条件走不同的分支,我们是不是可以把枚举的type变成一个list,然后策略粒度更细一点,是不是可以定义一组不同的流程?
高级玩法还有很多,需要根据业务需求来实现,本文只是简单聊了一下这种思想,如果碰到这种场景,可以试试看。
转载请注明:迷路的老鼠 » 利用Aviator和策略模式实现一套简单的流程系统