简介
责任链模式( Chain of Responsibility Pattern)属于行为型设计模式。顾名思义就是生成一个“链”,然后让请求沿着链传递,传递的过程其实就是挨个进入和离开并列的节点中,这些节点被称之为“责任”,每一个节点有权力自己判断是否进行处理。请求者无需关心,也无法知道到底有多少个处理节点。
优点:
- 只需要将请求发送到链上即可,⽆须关⼼请求的处理细节,降低了耦合度
- 链中节点可以设置顺序,增加了灵活度
- 链中的节点可以增减,增加了可扩展性
- 简化了对象之前的连接,可避免出现众多的if或者 if-else 情况
- 一个节点处理某一个细粒度任务,符合单一职责原则
缺点:
- 责任作为处理单元,会分散到各个节点中,每一个节点功能单一,多个节点共同完成了一个处理逻辑,节点越多越膨胀
- 可能会出现请求跑完整个链路都得不到处理的情况
- 增加了请求者复杂度,由原来的请求资源改为了请求一个链路
类图
应用场景
- SpringBoot 框架中的各种拦截器、各种过滤器,如 ApplicationFilterChain
日志处理
参数校验
举例
以SpringBoot项目举例如何在代码中编写责任链模式
业务描述
一个消息推送的业务场景,当订单创建后需要向多个端推送消息,如站内消息、微信模板消息等。
- 请求者:(客户端)就是订单创建者
- 责任:推送站内消息、推送微信模板消息。做具体业务实现的对象称之为责任
- 链:维护各个“责任”,让他们协同工作,哪些责任需要被创建、如何排序、链路如何传递等
代码实现
本例中用到了filter作为链节点的修饰词,这里是参考了SprinBoot 框架 ApplicationFilterChain 的命名,其实更加合适的名词是 handler
定义消息通知接口
public interface NoticeFilter {
Integer getShort();
void pushMsg(AbstractNoticeDto model, NoticePushChain chain , Integer index);
}
- 此接口为各个责任类的共同父接口,用来定义消息通知的规范
- getShort 方法获取本责任的排序,需要实现类自行实现
- 定义了一个pushMsg 的方法,用来实际干活,将消息推送到特定的平台。此方法入参中有一个 chain ,用来在运行时获取处理链路
创建站内消息通知实现
@Slf4j
@AllArgsConstructor
@Service
public class MsgNoticeFilter implements NoticeFilter {
@Override
public Integer getShort() {
return 1;
}
@Override
public void pushMsg(AbstractNoticeDto model, NoticePushChain chain, Integer index) {
log.debug("执行 msg 消息推送");
//msgNoticeService.createMessage(model);
chain.doFilter(model, chain, index);
}
}
- MsgNoticeFilter 类为站内消息推送实现类
- 重写 getShort 方法,设置自己的顺序
- 重写 pushMsg 方法,编写具体的消息推送逻辑
- 消息推送完毕之后,调用 chain.doFilter 方法,将请求沿着链路传递
创建微信消息通知实现
@Slf4j
@AllArgsConstructor
@Service
public class WechatNoticeFilter implements NoticeFilter {
@Override
public Integer getShort() {
return 2;
}
@Override
public void pushMsg(AbstractNoticeDto model, NoticePushChain chain, Integer index) {
log.debug("执行 wechat 消息通知");
//wechatNoticeService.pushNotice(model);
chain.doFilter(model, chain, index);
}
}
- 思路和 MsgNoticeFilter 一样
创建责任链实现类
@Service
public class NoticePushChain implements ApplicationContextAware {
private List<NoticeFilter> filters;
/**
* 当前过滤器 index
*/
private int currentIndex = 0;
/**
* 下一个过滤器 index
*/
private int nextIndex = 0;
/**
* 总过滤器数量
*/
private int total = 0;
public void doFilter(AbstractNoticeDto model, NoticePushChain chain, Integer index) {
if (index == null || index < 0) {
index = 0;
}
if (index < filters.size()) {
nextIndex = index + 1;
currentIndex = index;
index++;
filters.get(index - 1).pushMsg(model, chain, index);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, NoticeFilter> serviceMap = applicationContext.getBeansOfType(NoticeFilter.class);
filters = new ArrayList<>(serviceMap.values());
filters = filters.stream().sorted(Comparator.comparing(NoticeFilter::getShort)).collect(Collectors.toList());
total = filters.size();
}
}
- 创建NoticePushChain类用来维护各个责任,此类需要注入到容器中,供请求者调用。有了链路之后,请求者由原来的调用 service ,改为调用链路,某种层面来说,链路也是提供服务 ,有点像 DDD 中的 application层的概念
- 定义变量filters,存放所有可用的责任实现类,为一个list
- 定义 currentIndex 用来在运行时记录当前处理节点的索引
- 定义nextIndex 用来在运行时记录下一个处理节点的索引
- 定义total 用来在运行时记录一共有多少个处理节点
此类需要实现 ApplicationContextAware 接口,用于在程序启动完毕后收集所有的责任实现类,这里就可以做业务逻辑处理扩展,比如哪些责任实现类需要存放到 filters 中可以自定义
重写 setApplicationContext 方法,并通过 getBeansOfType 方法获取责任实现类
通过对 filters 遍历来实现为链路上所有节点排序
定义 doFilter 方法,此方法和节点能够沿着链路传递的核心,参数中的 chain 会在各个处理节点中挨个向下传递,每处理完一个节点就回调 doFilter 方法,再由控制 index实现链路向下传递。此方法需要暴露出去,供请求者调用
- 本文中说到的 节点和责任是一个概念,都是指实际处理业务逻辑的类
模拟调用
@Autowired
NoticePushChain noticePushChain;
@GetMapping("/createOrder")
public String createOrder() {
AbstractNoticeDto model = new AbstractNoticeDto();
noticePushChain.doFilter(model, noticePushChain, null);
return IdUtil.fastSimpleUUID();
}
- 注入NoticePushChain 并模拟调用
- 请求先到 chain ,而后将根据顺序进入第一个节点,第一个节点处理完毕,回到chain ,而后进入第二个节点,依此类推。
代码
https://gitee.com/naylor_personal/ramble-spring-boot/tree/master/chain-pattern