設(shè)計(jì)模式——責(zé)任鏈模式
一. 簡(jiǎn)介
責(zé)任鏈模式(Chain of Responsibility) 是行為型設(shè)計(jì)模式之一,其將鏈中每一個(gè)節(jié)點(diǎn)看作是一個(gè)對(duì)象,每個(gè)節(jié)點(diǎn)處理的請(qǐng)求均不同,且內(nèi)部自動(dòng)維護(hù)一個(gè)下一節(jié)點(diǎn)對(duì)象。當(dāng)一個(gè)請(qǐng)求從鏈?zhǔn)降氖锥税l(fā)出時(shí),會(huì)沿著鏈的路徑依次傳遞給每一個(gè)節(jié)點(diǎn)對(duì)象,直至有對(duì)象處理這個(gè)請(qǐng)求為止。
主要解決
責(zé)任鏈模式 解耦了請(qǐng)求與處理,客戶只需將請(qǐng)求發(fā)送到鏈上即可,無需關(guān)心請(qǐng)求的具體內(nèi)容和處理細(xì)節(jié),請(qǐng)求會(huì)自動(dòng)進(jìn)行傳遞直至有節(jié)點(diǎn)對(duì)象進(jìn)行處理。
二. 優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 解耦了請(qǐng)求與處理;
- 請(qǐng)求處理者(節(jié)點(diǎn)對(duì)象)只需關(guān)注自己感興趣的請(qǐng)求進(jìn)行處理即可,對(duì)于不感興趣的請(qǐng)求,直接轉(zhuǎn)發(fā)給下一級(jí)節(jié)點(diǎn)對(duì)象;
- 具備鏈?zhǔn)絺鬟f處理請(qǐng)求功能,請(qǐng)求發(fā)送者無需知曉鏈路結(jié)構(gòu),只需等待請(qǐng)求處理結(jié)果;
- 鏈路結(jié)構(gòu)靈活,可以通過改變鏈路結(jié)構(gòu)動(dòng)態(tài)地新增或刪減責(zé)任;
- 易于擴(kuò)展新的請(qǐng)求處理類(節(jié)點(diǎn)),符合 開閉原則;
缺點(diǎn)
- 責(zé)任鏈路過長(zhǎng)時(shí),可能對(duì)請(qǐng)求傳遞處理效率有影響;
- 如果節(jié)點(diǎn)對(duì)象存在循環(huán)引用時(shí),會(huì)造成死循環(huán),導(dǎo)致系統(tǒng)崩潰;
三.使用場(chǎng)景
- 多個(gè)對(duì)象可以處理同一請(qǐng)求,但具體由哪個(gè)對(duì)象處理則在運(yùn)行時(shí)動(dòng)態(tài)決定;
- 在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求;
- 可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求;
三.場(chǎng)景demo
首先來看下 責(zé)任鏈模式 的通用 UML 類圖:

從 UML 類圖中,我們可以看到,責(zé)任鏈模式 主要包含兩種角色:
- 抽象處理者(Handler):定義一個(gè)請(qǐng)求處理的方法,并維護(hù)一個(gè)下一個(gè)處理節(jié)點(diǎn) Handler 對(duì)象的引用;
- 具體處理者(ConcreteHandler):對(duì)請(qǐng)求進(jìn)行處理,如果不感興趣,則進(jìn)行轉(zhuǎn)發(fā);
以下是 責(zé)任鏈模式 的通用代碼:
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);
}
}
}
}
上面的代碼中,其實(shí)我們把消息硬編碼為String類型,而真實(shí)業(yè)務(wù)中,消息是具備多樣性的,可以是int、String或者是自定義類型····因此,我們可以在上面代碼中的基礎(chǔ)上,將消息類型進(jìn)行抽象Request,增強(qiáng)了消息的包容性。
責(zé)任鏈模式 的本質(zhì)是:解耦請(qǐng)求與處理,讓請(qǐng)求在處理鏈中能進(jìn)行傳遞與被處理;理解 責(zé)任鏈模式 應(yīng)當(dāng)理解的是其模式(道)而不是其具體實(shí)現(xiàn)(術(shù)),責(zé)任鏈模式 的獨(dú)到之處是其將節(jié)點(diǎn)處理者組合成了鏈?zhǔn)浇Y(jié)構(gòu),并允許節(jié)點(diǎn)自身決定是否進(jìn)行請(qǐng)求處理或轉(zhuǎn)發(fā),相當(dāng)于讓請(qǐng)求流動(dòng)了起來。
個(gè)人認(rèn)為:責(zé)任鏈模式就相當(dāng)于一個(gè)鏈表的遞歸調(diào)用
三.OkHttp源碼之?dāng)r截器鏈
對(duì)于okhttp來說,它的攔截器各位肯定是非常熟悉的,正是由于它的存在,okhttp的擴(kuò)展性極強(qiáng),對(duì)于調(diào)用方來說可謂是非常友好。而實(shí)現(xiàn)這個(gè)攔截器的就是大名鼎鼎的責(zé)任鏈模式了,其實(shí)整個(gè)okhttp的核心功能都是基于這個(gè)攔截器的,由各種不同的攔截器實(shí)現(xiàn)的。所以今天我們分析下okhttp的責(zé)任鏈模式。
基本源碼
對(duì)于okhttp的Call來說,發(fā)起請(qǐng)求最終其實(shí)都調(diào)用了一個(gè)方法獲取到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);
}
前面只是加入一些必要的攔截器,是一些具體功能的實(shí)現(xiàn),我們今天并不是分析這些具體功能,而是分析這種特殊的架構(gòu),所以這些攔截器就略過,我們重點(diǎn)看這里
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
核心就是構(gòu)造了一個(gè)chain,這個(gè)chain我們重點(diǎn)關(guān)注的是interceptors和index(此時(shí)是第一個(gè)頭節(jié)點(diǎn),傳入的是0),也就是 RetryAndFollowUpInterceptor,然后調(diào)用chain.proceed()方法獲取最終結(jié)果。
那么我們繼續(xù)跟進(jìn)去:
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;
})
可以看到,每一個(gè)chain都是鏈條上的一個(gè)節(jié)點(diǎn),chain的proceed()方法首先是獲取下一個(gè)節(jié)點(diǎn),然后獲取當(dāng)前節(jié)點(diǎn)對(duì)應(yīng)的攔截器,將下一個(gè)節(jié)點(diǎn)當(dāng)做參數(shù)給了攔截器的intercept()方法,在interceptor中會(huì)手動(dòng)調(diào)用下一個(gè)chain的chain.proceed方法,將這條鏈走下去。這里有一點(diǎn)要注意,此處是先獲取的下一個(gè)chain,也就是說我們?cè)诘趎個(gè)攔截器中會(huì)調(diào)用第n+1個(gè)chain的proceed()方法,這樣才能做到請(qǐng)求之前添加自定義行為(調(diào)用第n+1個(gè)chain的proceed()之前)和請(qǐng)求之后(調(diào)用第n+1個(gè)chain的proceed()之后)添加自定義行為。
疑問
現(xiàn)在,大家應(yīng)該都知道這條鏈?zhǔn)窃趺磦飨氯サ模蠹铱赡軙?huì)感到很好奇,為什么這里會(huì)有兩個(gè)角色,interceptor和chain,而且這兩個(gè)結(jié)構(gòu)似乎不是傳統(tǒng)意義上的client和handler的角色(經(jīng)典的責(zé)任鏈模式主要是這兩個(gè)角色),因?yàn)檫@里interceptor中會(huì)調(diào)用chain的方法,這一行為似乎有點(diǎn)反常(此處的client角色應(yīng)該是RealCall)。由于暴露給外界的接口其實(shí)是interceptor,這里我們嘗試拋棄chain看會(huì)怎樣,下面是一個(gè)鏈表結(jié)構(gòu)的經(jīng)典責(zé)任鏈實(shí)現(xiàn)
public class InterceptorChain {
Response proceed(Request request){
//此處意味著拿到第一個(gè)節(jié)點(diǎn),具體怎么拿到不用管,僅僅做示意
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;
}
}
}
經(jīng)典的責(zé)任鏈兩個(gè)角色,client(此處的InterceptorChain)和handler(此處的Interceptor)
此處我們嘗試去實(shí)現(xiàn)okhttp的日志攔截器,主要實(shí)現(xiàn):
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這個(gè)類,我們只是借助interceptor也是能實(shí)現(xiàn)現(xiàn)有的結(jié)構(gòu)的。那么這兩種結(jié)構(gòu)有本質(zhì)區(qū)別嗎?我們?cè)倏聪抡5膐khttp的日志攔截器:
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;
}
}
可以看到代碼幾乎一模一樣,只是一個(gè)直接調(diào)用interceptor實(shí)現(xiàn),一個(gè)通過chain來實(shí)現(xiàn)的,然而區(qū)別就在于這個(gè)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;
}
總體看下來,本質(zhì)區(qū)別其實(shí)就一個(gè),okhttp的proceed()方法會(huì)被執(zhí)行多次(在每個(gè)interceptor中都會(huì)調(diào)用chain.proceed()獲取Response),每一個(gè)interceptor都會(huì)執(zhí)行該方法,所以在該方法中我們可以對(duì)interceptor的實(shí)現(xiàn)做很多檢查,做出一些約束。然而我們自己DIY的結(jié)構(gòu),我們最理想的也只是把這些檢查封裝成一種util,由interceptor的實(shí)現(xiàn)人員手動(dòng)調(diào)用,然而靠interceptor開發(fā)人員調(diào)用的話很容易遺漏,所以這里應(yīng)該是借用了代理的思想,將它封裝在了chain中,必須通過chain的proceed()方法獲取Response,這些就能由框架做這些檢查和約束了。
總結(jié)
okhttp的這套責(zé)任鏈模式的實(shí)現(xiàn),從代碼上來看確實(shí)復(fù)雜了不少,理解起來沒那么容易,但是,這套模式能夠?qū)γ總€(gè)interceptor的行為做出一些基本的規(guī)范和檢查,而不是都扔給interceptor的實(shí)現(xiàn)人員去做,這點(diǎn)收益就大了,稍微難理解一點(diǎn)也是可以的,當(dāng)然我們更應(yīng)該學(xué)習(xí)的是這種將約束和檢查收斂到框架中的這種想和實(shí)現(xiàn)思路,比如這里就打破了常規(guī)的責(zé)任鏈,利用了代理的思想引入了一個(gè)chain。