策略模式
在策略模式 (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
来源: https://www.cnblogs.com/catlkb/p/11482001.html