最新消息:从今天开始,做一个有好习惯的人。

利用Aviator和策略模式实现一套简单的流程系统

解决方案 迷路的老鼠 7037浏览 5评论

业务开发时,我们经常会面对很多复杂且长的流程,整个流程写下来可能,这个流程可能是一次执行,也可能是分节点,节点需要每次手动触发,那么我们的代码会很复杂,而且对于新手也很难掌握。同时,业务需求可能经常发生变更,又或者我们需要创建同样类似的流程,不得不对原代码频繁修改,经过几个迭代,可能没人愿意面对这堆代码。

那么是否有一种方法可以将复杂流程解耦,做成可配置化、可复用的通用流程呢,本文将提供一种解决方案,当然仁者见仁,智者见智,方法好不好很难说,但是总能学到一些东西。

本方案采用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和策略模式实现一套简单的流程系统

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (5)

  1. 诶呀~立马想到硬币分拣原理了 http://image107.360doc.com/DownloadImg/2017/06/1101/101416904_3
    wa5年前 (2019-10-13)回复
    • 为啥地址打不开?
      迷路的老鼠5年前 (2019-10-14)回复
      • 地址这么快失效了?私信你
        wa5年前 (2019-10-14)回复
        • 理解能力真强,很形象
          迷路的老鼠5年前 (2019-10-17)回复
  2. 高端大气上档次的思路
    Mrs红豆4年前 (2020-03-22)回复