策略模式实战

策略模式

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
主要解决在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

策略模式的定义网上很多文章都有详细的说明。这次很巧,我在项目中也遇到一个需要很多if..else才能解决的问题。
优秀的我很快想到策略模式。


问题描述

业务上出现多个审批流,比如增加设备、回收设备、转移设备等等申请。所有申请的点击操作都是一样的(通过、不通过)。
面对这种情况我们有多种选择:

  • 第一种方法:可以将所有的操作按照审批类型分别调用接口。
    例如增加设备申请审批通过使用 "/addSuccess",而回收设备申请审批通过使用 "/recycleSuccess".
    这样做的优点是清晰明了,一个类型调用一个接口。缺点是工作量大,需要做很多重复的工作。

  • 第二种方法:前端使用一个接口,例如"/flow/handleApproval"。
    前段将审批的结果、类型返回给后端,后端通过if...else...进行多个选择。
    这样做的优点是工作量减少,缺点是需要写很多的if...else...,并且这样的代码不易维护,如果需要增加一个类型就需要增加一个 if...else..的判断。代码不够优雅。

  • 第三种方法:就是我们的策略模式啦。通过上下文动态地选择能够处理需求的方法。
    至于怎么动态?请看下面分解


思维导图

策略模式实战_第1张图片


代码演示

第一步, 自定义注解

自定义一个注解。这个注解标注在handler上,目的是在Spring初始化容器时,能够将handler正确注册到容器上,维护一个map列表(其实就相当于一个工厂)。其中key是HandlerType中的value值,value是handler实例。

/**
 * @Description 流程处理类型
 * @Author lkb
 * @CreateDate: 2019/5/22
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface HandlerType {
    String[] value();
}
第二步, 定义流程处理接口

FlowHandler接口定义了所有handler处理类都必须实现的方法。

public interface FlowHandler {

    /**
     * 功能描述: 处理流程
     * @author lkb
     * @date 2019/8/5
     * @param userId 当前用户id
     * @param flowJobId 流程id
     * @param statusCode 2 审批通过、3 审批不通过
     * @return int 成功返回1  失败返回0
     */
    int doHandle(Integer userId, String flowJobId, Integer statusCode, String comment);
}
第三步, 定义抽象工厂类

AbstractFlowHandlerFactory 进一步规定处理类需要实现的方法,比如审批通过/审批不通过。

@Slf4j
public abstract class AbstractFlowHandlerFactory implements FlowHandler {

    /**
     * 备注信息
     */
    private String comment;

    @Override
    public final int doHandle(Integer userId, String flowJobId, Integer statusCode, String comment) {
        int ret = 0;
        this.comment = comment;
        switch (statusCode) {
            case FlowConts.APPROVAL_STATUS_OK:
                ret = auditPass(userId, flowJobId);
                break;
            case FlowConts.APPROVAL_STATUS_FAIL:
                ret = auditNotPass(userId, flowJobId);
                break;
            default:
                log.error("Status code error. statusCode = {}", statusCode);
        }
        return ret;
    }

    /**
     * @author slm
     * @return 返回审批通过或者不通过的备注信息
     */
    protected String getComment() {
        return this.comment;
    }


    /**
     * 审核通过
     *
     * @param userId 用户id
     * @param flowJobId 流程编码
     * @return 操作状态码
     */
    protected abstract int auditPass(Integer userId, String flowJobId);

    /**
     * 审核不通过
     *
     * @param userId 用户id
     * @param flowJobId 流程编码
     * @return 操作状态码
     */
    protected abstract int auditNotPass(Integer userId, String flowJobId);
}
第四步, 增加具体的处理实例
/**
 * @Description 新增设备处理
 * @Author lkb
 * @CreateDate: 2019/8/12
 */
@Component
@HandlerType("zjsbz")
public class DeviceAddHandler extends AbstractFlowHandlerFactory {

    @Autowired
    private IDeviceAddService service;

    /**
     * 审核通过
     *
     * @param userId    用户id
     * @param flowJobId 流程编码
     * @return 操作状态码
     */
    @Override
    protected int auditPass(Integer userId, String flowJobId) {
        return service.addDevice(userId,flowJobId);
    }

    /**
     * 审核不通过
     *
     * @param userId    用户id
     * @param flowJobId 流程编码
     * @return 操作状态码
     */
    @Override
    protected int auditNotPass(Integer userId, String flowJobId) {
        return service.cancelAddDevice(userId,flowJobId);
    }
}
第五步, 定义流程控制上下文,维护一个工厂map.

除了下面的增加设备处理类还有替换、回收等等处理类。不同的处理类根据类型处理不同的业务逻辑。
FlowContext 类内部维护了一个map数据结构。这个map实际就是我们处理类的工厂类。
我们在bean容器初始化的时候将处理类实例加入工厂中(map中),需要的时候通过key-value的方式随时获取。

/**
 * @Description 流程控制上下文
 * @Author lkb
 * @CreateDate: 2019/8/5
 */
@Component
public class FlowContext {

    private Map map = new HashMap<>();

    private Logger logger = LoggerFactory.getLogger(FlowContext.class);

    public void setMap(String key, FlowHandler handler){
        if(map.get(key) == null){
            map.put(key, handler);
        }
    }

    public FlowHandler getHandler(String type){
        if(null == map){
            logger.error("map is null.");
            throw new BusinessException(ErrorCodeEnum.SYS_ERROR);
        }
        FlowHandler clazz = map.get(type);
        if(null == clazz) {
            logger.error("can not find flowHandler by type, type = {}", type);
            throw new BusinessException(ErrorCodeEnum.PARAM_ERROR);
        }
        return clazz;
    }
}
第六步, 初始化工厂,也就是填充map。

这里我们用到Spring中的 BeanPostProcessor
继承这个接口重写 postProcessAfterInitialization。
通过获取注解中的值,将对应的值作为key,对应的处理实例作为value,初始化map。

@Component
public class HandlerProcessor implements BeanPostProcessor {

    @Autowired
    private FlowContext context;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean.getClass().isAnnotationPresent(HandlerType.class)){
            HandlerType handlerType = bean.getClass().getAnnotation(HandlerType.class);
            if(null != handlerType){
                String[] values = handlerType.value();
                if(bean instanceof FlowHandler){
                    for(String value : values){
                        context.setMap(value,(FlowHandler) bean);
                    }
                }
            }
        }

        return bean;
    }
}
第七步, 使用啦

经过上面六步,我们就可以开心的使用策略模式写出优雅的代码了。♪(^∀^●)ノ

    /**
     * 审批结束回调.
     * @author lkb
     * @date 2019/8/5
     * @param
     * @return com.laidian.erp.common.vo.BaseReturnVO
     */
    @PostMapping("/handleApproval")
    public BaseReturnVO handleApproval(@Valid @NotNull(message = "userId为空") Integer userId, @NotNull(message = "flowJobId为空")String flowJobId,
                                       @NotNull(message = "statusCode为空") Integer statusCode, @NotNull(message = "type为空")String type, String comment){
        logger.info("flowJobId [{}] done, statusCode :[{}]",flowJobId,statusCode);
        deviceOperApplyTransactService.updateStatus(flowJobId, statusCode);
        return flowService.handleApproval(userId, flowJobId, statusCode, type, comment) == 1 ? BaseReturnVO.success() : BaseReturnVO.failure();
    }

    /**
     * 功能描述: 处理审批
     *
     * @param userId
     * @param flowJobId
     * @param codeStatus
     * @param type
     * @return void
     * @author lkb
     * @date 2019/8/5
     */
    @Override
    public int handleApproval(Integer userId, String flowJobId, Integer codeStatus, String type, String comment) {
        FlowHandler flowHandler = flowContext.getHandler(type);
        if(null == flowHandler){
            return 0;
        }
        return flowHandler.doHandle(userId,flowJobId,codeStatus, comment);
    }

即使我们新增一个流程,也不需要改动原来的代码了,只需要增加一个handler就ok啦。
是不是很方便?大家是否get到?

the end

你可能感兴趣的