0x0、引言
本文对应设计模式与范式:行为型(62-63),责任链模式 (Chain of Responsibility Pattern),常用于框架开发中,为框架提供扩展点,让框架使用者在不修改框架源码的情况下,基于扩展点添加新的功能,具体点说,最常用来开发框架的 拦截器 和 过滤器。
Tips:二手知识加工难免有所纰漏,感兴趣有时间的可自行查阅原文,谢谢。
0x1、定义
原始定义
将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,知道链上某个接收对象能够处理它为止。
定义看起来有点抽象,简单点就是:构建一个处理流水线来对一次请求进行多次处理。
还是不懂?没关系,写个简单例子帮助理解~
0x2、写个简单例子
你问哥哥、粑粑、麻麻拿钱,门槛依次是100,500,1000,只能按照一层层往下走,比如:
100块以下的开销,你可以找哥哥解决,100以上500以下的你得找爸爸,500以上1000以下你得找麻麻~
直接if-else一把梭,不难写出这样的代码:
public class ChainTest {
public static void main(String[] args) {
Random random = new Random();
int needMoney = random.nextInt(1500);
System.out.println("需要:" + needMoney + "块~");
if(needMoney < 100) {
System.out.println("哥哥:小于100块哥哥还是有的,给你~");
} else {
System.out.println("哥哥:大于100块哥哥木有那么多钱,找粑粑去吧~");
if(needMoney < 500) {
System.out.println("粑粑:500块以内,粑粑有,给你~");
} else {
System.out.println("粑粑:大于500,粑粑木有,找妈妈去吧~");
if(needMoney < 1000) {
System.out.println("麻麻:1000以内,麻麻可以报销,给你~");
} else {
System.out.println("麻麻:你要那么多钱干嘛?");
}
}
}
}
}
代码运行输出结果如下:
如果再来个疼爱孙子的爷爷,超过1000的可以找他报销,又得嵌套一个if-else,在一些复杂的实际业务场景,这样的写法可能要套十几层,对于这种流水线式加工的场景,其实可以使用责任链模式解耦~
把伸手要钱这个行为看成一个 请求,你是 请求者,家人是 接收者,他们会按照特定的顺序:哥哥 → 粑粑 → 麻麻 → 爷爷 对你的请求进行处理,在自己额度范围内,就不往下走,不在就往下走,直到最后一个接收者为止。这个特定顺序可以看成一条 链,请求就是沿着这样的链往后传递~
一种最简单的实现方式,就是:每个接收者持有后继接收者实例,递归调用直到没有后继接收者为止,实现代码如下:
// 抽象处理者
public abstract class AbstractHandler {
// 下一个处理者
private AbstractHandler nextHandler;
public AbstractHandler getNextHandler() { return nextHandler; }
public void setNextHandler(AbstractHandler nextHandler) { this.nextHandler = nextHandler; }
// 请求处理
public abstract void handleRequest(String msg, int money);
}
// 具体处理者
public class BrotherHandler extends AbstractHandler {
@Override
public void handleRequest(String msg, int money) {
System.out.println(msg + ":" + money + "块~");
if(money < 100) {
System.out.println("哥哥:小于100块哥哥还是有的,给你~");
} else {
System.out.println("哥哥:大于100块哥哥木有那么多钱,找粑粑去吧~");
if(getNextHandler() != null) getNextHandler().handleRequest(msg, money);
}
}
}
public class FatherHandler extends AbstractHandler {
@Override
public void handleRequest(String msg, int money) {
if(money < 500) {
System.out.println("粑粑:500块以内,粑粑有,给你~");
} else {
System.out.println("粑粑:大于500,粑粑木有,找妈妈去吧~");
if(getNextHandler() != null) getNextHandler().handleRequest(msg, money);
}
}
}
public class MotherHandler extends AbstractHandler {
@Override
public void handleRequest(String msg, int money) {
if(money < 1000) {
System.out.println("麻麻:1000以内,麻麻可以报销,给你~");
} else {
System.out.println("麻麻:你要那么多钱干嘛?");
if(getNextHandler() != null) getNextHandler().handleRequest(msg, money);
}
}
}
// 测试用例
public class ChainTest {
public static void main(String[] args) {
Random random = new Random();
int needMoney = random.nextInt(1500);
BrotherHandler brotherHandler = new BrotherHandler();
FatherHandler fatherHandler = new FatherHandler();
MotherHandler motherHandler = new MotherHandler();
// 指定下一个接收者
brotherHandler.setNextHandler(fatherHandler);
fatherHandler.setNextHandler(motherHandler);
// 开始请求传递
brotherHandler.handleRequest("狗子要钱", needMoney);
}
}
代码运行结果输出如下:
责任链模式解耦后,此时我们要再添加一个爷爷就很简单了,四步:
继承AbstractHandler → 重写handleRequest() → 初始化爷爷实例 → motherHandler.setNextHandler()
如果要再来个曾祖父或高曾祖父,也是这样操作,你可能会吐槽是不是有点过度设计,简单的if-else嵌套就能解决,这样会写多很多类,有点复杂化了。
应用设计模式主要是为了:应付代码复杂性,让其满足开闭原则,提高代码扩展性。而将大块代码逻辑拆解成函数、大类拆分成小类,是应对代码复杂性的常用方法。
此处使用责任链模式,把处理请求的函数拆分出来,设计成独立的类,简化了ChainTest类,使其不至于代码过多,过复杂。
客户端代码新增一个接收者,不需要去修改框架的代码,只需基于框架提供的扩展点进行扩展,可以说是:在框架代码范围内实现了开闭原则。
老规矩,带出UML类图、组成角色、使用场景及优缺点的总结
- Handler (抽象处理者) → 定义了处理请求的接口或抽象类,提供处理请求的方法和设置下一个处理者的方法;
- ConcreteHandler (具体处理者) → 抽象处理者的具体实现,按照链条顺序对请求进行具体处理;
使用场景
- 运行时需要动态使用多个关联对象来处理同一次请求,如编译打包发布流程;
- 不想让使用者知道具体的处理逻辑,如权限校验的登录拦截器;
- 需动态替换流程处理中的流程对象;
- if-else多层嵌套或冗长的switch结构解耦;
优点
- 降低客户端(请求者)与处理链条上对象(接收者)间的耦合度;
- 动态组合,简化了对象前后关联处理的复杂性,只需存储一个指向后继者的引用;
- 扩展灵活,新增具体请求者时无需修改原有系统的代码,满足开闭原则;
缺点
- 产生许多细颗粒对象;
- 比较长的责任链,可能涉及多个处理对象,可能存在性能问题,及调试不方便;
- 建链不当,可能造成循环调用,导致死循环,进而导致堆栈溢出错误;
另外,除了使用上述 递归的方式形成链条 实现责任链模式外,还可以使用数据结构(数组、列表、链表等) 按顺序保存具体处理者实例,然后遍历的方式实现~
0x3、责任链的 "纯与不纯"
- 纯责任链 → 要么承担全部责任,要么责任推给下家,不允许在某处承担了部分或全部责任,然后又把责任推给下家;
- 不纯责任链 → 责任在某处部分或全部处理后,还往下传递;
0x4、加餐:模式应用示例 → OkHttp拦截器核心原理
Android著名的第三方请求库OkHttp中的拦截器,就用到了责任链模式,我们来扒一扒具体的实现原理~
① 两个小知识点
通过继承 Interceptor
接口,重写intercept(Chain)函数来实现一个自定义拦截器,看下接口:
接着通过 addInterceptor()
函数添加自定义拦截器,跟下:
看下 interceptors
:
呕吼,拦截器列表,把自定义拦截器加到其中,看下哪里用到这个列表了:
这里有两个知识点:
- 1、利用Builder模式,将复杂对象与它的表示进行分离;
- 2、Util.immutableList 实际上调用的 Collections.unmodifiableList,用于构造一个不能修改的列表;
不能修改的列表,底层原理
继承List,对修改元素的函数进行重写,抛出UnsupportedOperationException异常
可以写个简单的代码验证下:
运行后报错,异常如下:
但真的就不可变吗,如果换成修改原列表呢?
运行打印结果如下:
操作原列表,导致不可变的列表发生了改变,看源码很好理解:
代理模式,即控制对目标对象的访问,UnmodifiableList只是对目标对象裹了一层,不经过它通过其他方式修改了目标对象,那肯定会发生改变啊。所以OkHttp不是直接调,而是:
基于原始列表,创建了一个 新的列表,杜绝了外部对目标对象的改动,真是妙啊!
② 怎么组成一条链
继续跟 networkInterceptors
,定义了一个公共获取拦截器列表的方法:
看下哪里用到了,定位到 RealCall.getResponseWithInterceptorChain()
中:
把拦截器全加到列表里,等下按顺序走,很好理解,然后是这个 RealInterceptorChain
,关注下传入参数,除了关注interceptors列表外,还要关注一个 index,传入的值为0。
然后下面调用了 chain.proceed(originalRequest)
返回了一个Response对象,点进去类看看:
核心代码是圈住那里,拆解下步骤:
- 创建一个新的RealInterceptorChain,还是传入拦截器列表,但 index + 1;
- 根据游标获取 当前的拦截器,调用拦截器的 intercept() 方法把新的Chain作为参数传入;
这里其实就是 递归
,先回到一开始的接口:
责任链模式有两个要点:如何往后传递处理结果 和 何时结束传递
第一点传递很好理解:
Interceptor实现类调用 intercept(Chain) 往下传递Chain实例,实例中包含了拦截器列表、下一个拦截器的下标、处理后的request实例 等。
第二点何时结束:
最后一个拦截器调用
chain.proceed()
返回Response实例为止,而这个实例会往前传递,前面调用了chain.proceed()
的拦截器可以获得Response实例并进行加工(如打日志),第一个调用了chain.proceed()
的拦截器得到的就是处理完后的Response。
所以OkHttp的拦截器/过滤器是 双向
的,而Chain接口就是链接其中的链条:
- 前 → 后:request()获得当前request实例,拦截器可对请求进行加工,可也调用intercept往下传递Chain;
- 后 → 前:proceed()获得当前response实例,拦截器可对响应进行加工,作为返回值往上传;
转载:https://juejin.cn/post/6993225793383956488
作者:coder_pig