策略模式實戰

  • 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