责任链模式
基本概念
责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
关于责任链的详细介绍可以点击这里进行了解。就不在这里过多叙述啦。
要来就来点实际的干货:在做公司项目(会员积分商城)项目时,会员模块有个会员降级任务,在开发这个功能的时候,我就将责任链模式集合业务融入了进去。
结合业务模式,降级策略配置有三个条件,当这三个条件有一满足则不执行降级,所以如果用if/else来操作那么应该会写很多,且逻辑不清晰,不够优雅。适当的运用设计模式,使逻辑清晰明了,也方便后续同学阅读。
业务的整体流程图如下:
抽象的职责节点的UML图如下:
上代码实战看看:
部分代码使用Test表示啦,大家都懂的哦
- 基础接口:BaseChainHandle.java
public interface BaseChainHandle {
/**
* 责任type
* @return
*/
Integer getChainType();
}
- 抽象父类:AbstractRelegationChainHandle.java
public abstract class AbstractRelegationChainHandle implements BaseChainHandle {
/**
* 下一个节点
*/
private AbstractRelegationChainHandle nextChain;
/**
* 对外暴露方法
* @return
*/
public abstract boolean execute(RelegationChainDTO dto);
public AbstractRelegationChainHandle getNextChain() {
return nextChain;
}
public AbstractRelegationChainHandle setNextChain(AbstractRelegationChainHandle nextChain) {
this.nextChain = nextChain;
return this.nextChain;
}
public AbstractRelegationChainHandle getCurChain() {
return this;
}
}
- 空节点子类:
设置该节点的意义主要是为了构造链表时的初始化首节点。
@Component
public class NotChainHandle extends AbstractRelegationChainHandle{
/**
* 设置空节点的意义是:该节点作为起始节点,不做任何操作,仅构造责任实例联时使用
* @return
*/
@Override
public Integer getChainType() {
return -1;
}
@Override
public boolean execute(RelegationChainDTO dto) {
if (Objects.nonNull(this.getNextChain())) {
// 向下传递
return this.getNextChain().execute(dto);
} else {
return Boolean.FALSE;
}
}
}
- 具体逻辑节点子类:(其他责任节点类似,不再枚举)
package com.test;
@Slf4j
@Component
public class Test1Handle extends AbstractRelegationChainHandle {
@Override
public Integer getChainType() {
return RelegationStrategyEnum.Test1.getCode();
}
@SneakyThrows
@Override
public boolean execute(RelegationChainDTO dto) {
// 业务逻辑处理...
if (Objects.nonNull(mkRecordTO)) {
if (//condition) {
return Boolean.TRUE;
}
}
if (Objects.nonNull(this.getNextChain())) {
// 向下传递
return this.getNextChain().execute(dto);
} else {
return Boolean.FALSE;
}
}
}
- 在工厂中将节点bean实例化,类似于策略模式使用@Autowire自动注入
package com.test;
/**
* @description 会员保级责任链 装配节点工厂
*/
@Component
public class RelegationChainHandleFactory {
/**
* 自动注入节点进来:必须是String,因为自动注入会将beanName设置为String
*/
@Autowired
private Map<String, AbstractRelegationChainHandle> relegationChainMap;
private final Map<Integer, AbstractRelegationChainHandle> relegationChainMapContext = new HashMap<>();
@PostConstruct
public void setRelegationChainMap() {
for (AbstractRelegationChainHandle obj : relegationChainMap.values()) {
relegationChainMapContext.put(obj.getChainType(), obj);
}
}
public AbstractRelegationChainHandle getChainHandleContext(Integer type) {
return relegationChainMapContext.get(type);
}
}
- 在业务逻辑中构造责任链:并执行节点逻辑
请看下方代码:为什么要初始化一个ThreadLocal变量来保存责任链:初始化一个ThreadLocal变量来保存该次动态责任链十分重要,具体原因可见代码中的相关注释内容。
package com.test;
......
/**
* 每一个子线程储存降级的动态责任链
*/
private ThreadLocal<AbstractRelegationChainHandle> startChain = new ThreadLocal<>();
.......
private void downLevelTest(Dto dto) {
// ... 业务逻辑处理
// 2. 匹配保级规则
// 2.1 构建保级规则责任链
/**
* 使用ThreadLocal储存该子线程的责任链:因为Spring实例化bean默认单例的,多线程环境下,bean的实例会被多个线程获取,
* 造成安全问题,所以使用ThreadLocal为每一个子线程分配一个空间,保存每一个子线程的动态责任链,隔离线程
*/
// 装载责任链节点
// 初始化责任链开始节点 type: -1
startChain.set(relegationChainHandleFactory.getChainHandleContext(-1));
// 手动置空:因为当其他线程实例化的该责任节点类的next可能会已经存在数据,我们要动态拼装责任链(下同)
startChain.get().setNextChain(null);
for (int i = 0; i < relegationStrategy.size(); i++) {
// 构造责任链:类似于构造链表的方式
// TODO 通过switch case不太优雅,目前暂时想到使用该方法进行构造责任链,待优化
// 尝试过其他写法来拼装(使用临时变量(多个ThreadLocal)等)
switch (i) {
case 0:
startChain.get().setNextChain(relegationChainHandleFactory.getChainHandleContext(relegationStrategy.get(i)));
startChain.get().getNextChain().setNextChain(null);
break;
case 1:
startChain.get().getNextChain()
.setNextChain(relegationChainHandleFactory.getChainHandleContext(relegationStrategy.get(i)));
startChain.get().getNextChain().getNextChain().setNextChain(null);
break;
case 2:
startChain.get().getNextChain().getNextChain()
.setNextChain(relegationChainHandleFactory.getChainHandleContext(relegationStrategy.get(i)));
startChain.get().getNextChain().getNextChain().getNextChain().setNextChain(null);
break;
default:
}
}
// ... 业务处理
} catch (Exception e) {
log.info("处理任务发生异常,account:{},e:{}", account, e);
} finally {
startChain.remove();
}
}
有些写法虽然也不太优雅,请见谅哈,鄙人水平有限,还需要多加学习!欢迎大家指出来一起探讨探讨
总结
设计模式虽然让代码看起来变多了很多,但是这样写可以使我们的逻辑清晰,并且方便扩展,提高代码的健壮性。
但有个问题需要提醒大家,在平时的开发过程中不能为了设计而设计,这样就会造成过度设计,反而起反作用,一定要结合自身业务来考虑,技术也是为了实现业务而服务的。