设计模式——责任链模式
一. 简介
责任链模式(Chain of Responsibility) 是行为型设计模式之一,其将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止。
主要解决
责任链模式 解耦了请求与处理,客户只需将请求发送到链上即可,无需关心请求的具体内容和处理细节,请求会自动进行传递直至有节点对象进行处理。
二. 优缺点
优点
- 解耦了请求与处理;
- 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一级节点对象;
- 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果;
- 链路结构灵活,可以通过改变链路结构动态地新增或删减责任;
- 易于扩展新的请求处理类(节点),符合 开闭原则;
缺点
- 责任链路过长时,可能对请求传递处理效率有影响;
- 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃;
三.使用场景
- 多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定;
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求;
- 可动态指定一组对象处理请求;
三.场景demo
首先来看下 责任链模式 的通用 UML 类图:
从 UML 类图中,我们可以看到,责任链模式 主要包含两种角色:
- 抽象处理者(Handler):定义一个请求处理的方法,并维护一个下一个处理节点 Handler 对象的引用;
- 具体处理者(ConcreteHandler):对请求进行处理,如果不感兴趣,则进行转发;
以下是 责任链模式 的通用代码:
class Client {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
handlerA.setnextHanlder(handlerB);
handlerA.handleRequest("requestB");
}
static abstract class Handler {
protected Handler mNextHandler;
public void setnextHanlder(Handler successor) {
this.mNextHandler = successor;
}
public abstract void handleRequest(String request);
}
static class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(String request) {
if ("requestA".equals(request)) {
System.out.println(String.format("%s deal with request: %s", this.getClass().getSimpleName(), request));
return;
}
if (this.mNextHandler != null) {
this.mNextHandler.handleRequest(request);
}
}
}
static class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(String request) {
if ("requestB".equals(request)) {
System.out.println(String.format("%s deal with request: %s", this.getClass().getSimpleName(), request));
return;
}
if (this.mNextHandler != null) {
this.mNextHandler.handleRequest(request);
}
}
}
}
上面的代码中,其实我们把消息硬编码为String类型,而真实业务中,消息是具备多样性的,可以是int、String或者是自定义类型····因此,我们可以在上面代码中的基础上,将消息类型进行抽象Request,增强了消息的包容性。
责任链模式 的本质是:解耦请求与处理,让请求在处理链中能进行传递与被处理;理解 责任链模式 应当理解的是其模式(道)而不是其具体实现(术),责任链模式 的独到之处是其将节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动了起来。
个人认为:责任链模式就相当于一个链表的递归调用
三.OkHttp源码之拦截器链
对于okhttp来说,它的拦截器各位肯定是非常熟悉的,正是由于它的存在,okhttp的扩展性极强,对于调用方来说可谓是非常友好。而实现这个拦截器的就是大名鼎鼎的责任链模式了,其实整个okhttp的核心功能都是基于这个拦截器的,由各种不同的拦截器实现的。所以今天我们分析下okhttp的责任链模式。
基本源码
对于okhttp的Call来说,发起请求最终其实都调用了一个方法获取到Response的,同步异步都是一样的:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
前面只是加入一些必要的拦截器,是一些具体功能的实现,我们今天并不是分析这些具体功能,而是分析这种特殊的架构,所以这些拦截器就略过,我们重点看这里
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
核心就是构造了一个chain,这个chain我们重点关注的是interceptors和index(此时是第一个头节点,传入的是0),也就是 RetryAndFollowUpInterceptor,然后调用chain.proceed()方法获取最终结果。
那么我们继续跟进去:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
//此处省略其他代码
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
//此处省略其他代码
return response;
})
可以看到,每一个chain都是链条上的一个节点,chain的proceed()方法首先是获取下一个节点,然后获取当前节点对应的拦截器,将下一个节点当做参数给了拦截器的intercept()方法,在interceptor中会手动调用下一个chain的chain.proceed方法,将这条链走下去。这里有一点要注意,此处是先获取的下一个chain,也就是说我们在第n个拦截器中会调用第n+1个chain的proceed()方法,这样才能做到请求之前添加自定义行为(调用第n+1个chain的proceed()之前)和请求之后(调用第n+1个chain的proceed()之后)添加自定义行为。
疑问
现在,大家应该都知道这条链是怎么传下去的,但大家可能会感到很好奇,为什么这里会有两个角色,interceptor和chain,而且这两个结构似乎不是传统意义上的client和handler的角色(经典的责任链模式主要是这两个角色),因为这里interceptor中会调用chain的方法,这一行为似乎有点反常(此处的client角色应该是RealCall)。由于暴露给外界的接口其实是interceptor,这里我们尝试抛弃chain看会怎样,下面是一个链表结构的经典责任链实现
public class InterceptorChain {
Response proceed(Request request){
//此处意味着拿到第一个节点,具体怎么拿到不用管,仅仅做示意
Interceptor first = getFirstInterceptor();
return first.intercept(request);
}
public abstract class Interceptor{
protected Interceptor next;
abstract Response intercept(Request request);
void setNext(Interceptor interceptor){
next = interceptor;
}
}
}
经典的责任链两个角色,client(此处的InterceptorChain)和handler(此处的Interceptor)
此处我们尝试去实现okhttp的日志拦截器,主要实现:
public class LogInterceptor extends Interceptor{
@Override
Response intercept(Request request) {
//此处getRequestParams是伪代码,仅做示意
Map<String,String> requestParams = request.getRequestParams();
Response response= next.intercept(request);
//此处getResult是伪代码,仅做示意
String result = response.getResult();
return response;
}
}
可以看到,没有chain这个类,我们只是借助interceptor也是能实现现有的结构的。那么这两种结构有本质区别吗?我们再看下正常的okhttp的日志拦截器:
public class LogInterceptor2 implements okhttp3.Interceptor{
@Override
public Response intercept(Chain chain) throws IOException {
//此处getRequestParams是伪代码,仅做示意
Request request = chain.request();
Map<String,String> requestParams = request.getRequestParams();
Response response= chain.proceed(request);
//此处getResult是伪代码,仅做示意
String result = response.getResult();
return response;
}
}
可以看到代码几乎一模一样,只是一个直接调用interceptor实现,一个通过chain来实现的,然而区别就在于这个chain.proceed()方法中:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
总体看下来,本质区别其实就一个,okhttp的proceed()方法会被执行多次(在每个interceptor中都会调用chain.proceed()获取Response),每一个interceptor都会执行该方法,所以在该方法中我们可以对interceptor的实现做很多检查,做出一些约束。然而我们自己DIY的结构,我们最理想的也只是把这些检查封装成一种util,由interceptor的实现人员手动调用,然而靠interceptor开发人员调用的话很容易遗漏,所以这里应该是借用了代理的思想,将它封装在了chain中,必须通过chain的proceed()方法获取Response,这些就能由框架做这些检查和约束了。
总结
okhttp的这套责任链模式的实现,从代码上来看确实复杂了不少,理解起来没那么容易,但是,这套模式能够对每个interceptor的行为做出一些基本的规范和检查,而不是都扔给interceptor的实现人员去做,这点收益就大了,稍微难理解一点也是可以的,当然我们更应该学习的是这种将约束和检查收敛到框架中的这种想和实现思路,比如这里就打破了常规的责任链,利用了代理的思想引入了一个chain。