策略模式實戰
- 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到?