設(shè)計(jì)模式——責(zé)任鏈模式
一. 簡介
責(zé)任鏈模式(Chain of Responsibility) 是行為型設(shè)計(jì)模式之一邪财,其將鏈中每一個(gè)節(jié)點(diǎn)看作是一個(gè)對象挽封,每個(gè)節(jié)點(diǎn)處理的請求均不同铁坎,且內(nèi)部自動(dòng)維護(hù)一個(gè)下一節(jié)點(diǎn)對象。當(dāng)一個(gè)請求從鏈?zhǔn)降氖锥税l(fā)出時(shí),會(huì)沿著鏈的路徑依次傳遞給每一個(gè)節(jié)點(diǎn)對象痴荐,直至有對象處理這個(gè)請求為止。
主要解決
責(zé)任鏈模式 解耦了請求與處理恰画,客戶只需將請求發(fā)送到鏈上即可宾茂,無需關(guān)心請求的具體內(nèi)容和處理細(xì)節(jié),請求會(huì)自動(dòng)進(jìn)行傳遞直至有節(jié)點(diǎn)對象進(jìn)行處理拴还。
二. 優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 解耦了請求與處理跨晴;
- 請求處理者(節(jié)點(diǎn)對象)只需關(guān)注自己感興趣的請求進(jìn)行處理即可,對于不感興趣的請求片林,直接轉(zhuǎn)發(fā)給下一級(jí)節(jié)點(diǎn)對象端盆;
- 具備鏈?zhǔn)絺鬟f處理請求功能怀骤,請求發(fā)送者無需知曉鏈路結(jié)構(gòu),只需等待請求處理結(jié)果焕妙;
- 鏈路結(jié)構(gòu)靈活蒋伦,可以通過改變鏈路結(jié)構(gòu)動(dòng)態(tài)地新增或刪減責(zé)任;
- 易于擴(kuò)展新的請求處理類(節(jié)點(diǎn))焚鹊,符合 開閉原則痕届;
缺點(diǎn)
- 責(zé)任鏈路過長時(shí),可能對請求傳遞處理效率有影響末患;
- 如果節(jié)點(diǎn)對象存在循環(huán)引用時(shí)研叫,會(huì)造成死循環(huán),導(dǎo)致系統(tǒng)崩潰璧针;
三.使用場景
- 多個(gè)對象可以處理同一請求嚷炉,但具體由哪個(gè)對象處理則在運(yùn)行時(shí)動(dòng)態(tài)決定;
- 在不明確指定接收者的情況下探橱,向多個(gè)對象中的一個(gè)提交一個(gè)請求申屹;
- 可動(dòng)態(tài)指定一組對象處理請求;
三.場景demo
首先來看下 責(zé)任鏈模式 的通用 UML 類圖:
從 UML 類圖中走搁,我們可以看到独柑,責(zé)任鏈模式 主要包含兩種角色:
- 抽象處理者(Handler):定義一個(gè)請求處理的方法,并維護(hù)一個(gè)下一個(gè)處理節(jié)點(diǎn) Handler 對象的引用私植;
- 具體處理者(ConcreteHandler):對請求進(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ì)是:解耦請求與處理彤侍,讓請求在處理鏈中能進(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)行請求處理或轉(zhuǎn)發(fā),相當(dāng)于讓請求流動(dòng)了起來闻书。
個(gè)人認(rèn)為:責(zé)任鏈模式就相當(dāng)于一個(gè)鏈表的遞歸調(diào)用
三.OkHttp源碼之?dāng)r截器鏈
對于okhttp來說名斟,它的攔截器各位肯定是非常熟悉的脑慧,正是由于它的存在,okhttp的擴(kuò)展性極強(qiáng)砰盐,對于調(diào)用方來說可謂是非常友好闷袒。而實(shí)現(xiàn)這個(gè)攔截器的就是大名鼎鼎的責(zé)任鏈模式了,其實(shí)整個(gè)okhttp的核心功能都是基于這個(gè)攔截器的楞卡,由各種不同的攔截器實(shí)現(xiàn)的霜运。所以今天我們分析下okhttp的責(zé)任鏈模式。
基本源碼
對于okhttp的Call來說蒋腮,發(fā)起請求最終其實(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)對應(yīng)的攔截器胰舆,將下一個(gè)節(jié)點(diǎn)當(dāng)做參數(shù)給了攔截器的intercept()方法骚露,在interceptor中會(huì)手動(dòng)調(diào)用下一個(gè)chain的chain.proceed方法,將這條鏈走下去缚窿。這里有一點(diǎn)要注意棘幸,此處是先獲取的下一個(gè)chain,也就是說我們在第n個(gè)攔截器中會(huì)調(diào)用第n+1個(gè)chain的proceed()方法倦零,這樣才能做到請求之前添加自定義行為(調(diào)用第n+1個(gè)chain的proceed()之前)和請求之后(調(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ū)別嗎缤剧?我們再看下正常的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;
}
}
可以看到代碼幾乎一模一樣荒辕,只是一個(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í)行該方法李皇,所以在該方法中我們可以對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飞袋。