策略模式实战
- 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到?