策略模式实战
Posted catlkb
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了策略模式实战相关的知识,希望对你有一定的参考价值。
策略模式
在策略模式(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
以上是关于策略模式实战的主要内容,如果未能解决你的问题,请参考以下文章
Springboot整合策略模式概念->使用场景->优缺点->企业级实战