設(shè)計(jì)模式——責(zé)任鏈模式

設(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

從 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。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容