策略模式实战

  • 2019 年 10 月 3 日
  • 筆記

策略模式

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

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


问题描述

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

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

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

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


思维导图

策略模式思维导图


代码演示

第一步, 自定义注解

自定义一个注解。这个注解标注在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<String , FlowHandler> 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