行為型模式(四):責任鏈模式

一页徐、 什么是責任鏈模式

責任鏈解取, 顧名思義是將多個節(jié)點通過鏈條的方式連接起來愈案,每一個節(jié)點相當于一個對象挺尾,而每一個對象層層相關(guān),直接或者間接引用下一個對象(節(jié)點)站绪;直到鏈條中有一個節(jié)點處理頭節(jié)點傳下來的事件截止潦嘶。

二、責任鏈模式使用場景(以下摘自 Android源碼設(shè)計模式)

有一事件崇众,可以被多個對象同時處理掂僵,但是由哪個對象處理則在運行時動態(tài)決定!
在請求處理者不明確時向多個對象中提交一個請求顷歌。
動態(tài)指定一組對象處理請求

三锰蓬、責任鏈模式UML圖

img

責任鏈模式.png

客戶端發(fā)出請求,調(diào)用抽象類Handler中的方法處理邏輯業(yè)務(wù)眯漩。對象ConcreteHandler1與ConcreteHandler2繼承Handler芹扭,其中ConcreteHandler1中持有下一個節(jié)點ConcreteHandler2的引用;事件由1對象發(fā)出赦抖,如果其處理不了舱卡,則交由2對象處理! 這是簡單的責任鏈模式結(jié)構(gòu)圖队萤,下面使用代碼的方式展現(xiàn):

Handler.class

/**
 * 抽象類
 */
public abstract class Handler {

    /**
     * 下一代處理者
     */
    public Handler nextProcessor;

    /**
     * 每一個實現(xiàn)類處理
     * 
     * @param msg
     */
    public abstract void handleRequest(String msg);
    
}

Processor1.class

/**
 * 處理者1
 */
public class Processor1 extends Handler {
    @Override
    public void handleRequest(String msg) {
        if(msg.equals("Processor1")) {
            System.out.println("第一個處理者處理");
        } else {
            nextProcessor.handleRequest(msg);
        }
    }
}

Processor2.class

/**
 * 處理者2
 */
public class Processor2 extends Handler {
    @Override
    public void handleRequest(String msg) {
        if(msg.equals("Processor2")) {
            System.out.println("第二個處理者處理");
        } else {
            nextProcessor.handleRequest(msg);
        }
    }
}

測試方法:

@Test
public void testProcessor() {
    Processor1 processor1 = new Processor1();
    Processor2 processor2 = new Processor2();

    processor1.nextProcessor = processor2;
    processor2.nextProcessor = processor1;

    processor1.handleRequest("Processor2");
    }

// 運行結(jié)果:
第二個處理者處理
Process finished with exit code 0

四轮锥、OKHttp中的責任鏈模式

<font color="red" size=1> 摘自百度百科</font>
android網(wǎng)絡(luò)框架之OKhttp
一個處理網(wǎng)絡(luò)請求的開源項目,是安卓端最火熱的輕量級框架,由移動支付Square公司貢獻(該公司還貢獻了Picasso)
用于替代HttpUrlConnection和Apache HttpClient(android API23 6.0里已移除HttpClient,現(xiàn)在已經(jīng)打不出來)

在使用OKHttp之前,我們可能用到更多的網(wǎng)絡(luò)請求是Async-Http要尔,一種用于異步處理網(wǎng)絡(luò)的框架舍杜,或者更加直接的是使用android自帶的HttpUrlConnection 和 HttpClient ,對其進行簡單的封裝赵辕; OKHttp開源出之后既绩,幾乎大部分項目都使用到這個開源框架,它有如下有點:

1. 官方在6.0以后添加了OKHttp
2. okHttp支持SPDY

// 同時能進行的最大請求數(shù)
private int maxRequests = 64;
// 同時請求的相同HOST的最大個數(shù)
private int maxRequestsPerHost = 5;

okhttp3.Dispatcher.class 中定義了這兩個變量还惠,并發(fā)數(shù)可以支持到64饲握,當然這兩個數(shù)值是可以自定義的,這說明OKHttp是支持SPDY的(<font color="#666666" size=1>谷歌開發(fā)的基于TCP的應用層協(xié)議,用于最小化網(wǎng)絡(luò)延遲,提升網(wǎng)絡(luò)速度,優(yōu)化用戶的網(wǎng)絡(luò)使用體驗. SPDY并不是一種替代http的協(xié)議,只是對http的一種增強</font>)

—— OKHttp的使用 同步獲取

private final OkHttpClient client = new OkHttpClient();
 
public void run() throws Exception {
    // 創(chuàng)建Request
    Request request = new Request.Builder()
        .url("http://www.baidu.com/")
        .build();
 
    // 獲取到結(jié)果
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    System.out.println(response.body().string());
}
—— 異步獲取
private final OkHttpClient client = new OkHttpClient();
 
public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://www.baidu.com/")
        .build();
 
    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Request request, Throwable throwable) {
        throwable.printStackTrace();
      }
 
      @Override public void onResponse(Response response) throws IOException {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
 
        Headers responseHeaders = response.headers();
        System.out.println(response.body().string());
      }
    });
}

使用過程很簡單,創(chuàng)建一個OKHttpClient救欧, 創(chuàng)建Request對象歪今,使用同步方法順序獲取或者使用回調(diào)CallBack方法異步獲取數(shù)據(jù);執(zhí)行的方法主要是client中newCall方法和enqueue方法颜矿,

——下面我們分析其中的源碼:
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override
public Call newCall(Request request) {
   return RealCall.newRealCall(this, request, false /* for web socket */);
}

newCall 方法需要傳一個Request寄猩,Request對象使用了構(gòu)建者模式將請求方法,請求體骑疆,請求頭進行了封裝; newCall 獲取到了Call 這個接口:

public interface Call extends Cloneable {
  /** 獲取到最開始的request */
  Request request();

  /** 執(zhí)行請求田篇,獲取到Response */
  Response execute() throws IOException;

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

而這個接口的 實現(xiàn)類只有 <font color="red">okhttp3.RealCall.class</font> ,接下來我們看下他的excute() 方法:

  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      // 將本次請求添加到事件調(diào)度器中
      client.dispatcher().executed(this);
        
      // 今天的主角箍铭, 責任鏈獲取到Response結(jié)果
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }
—— Dispatcher

在講解責任鏈之前泊柬,我們先看下Dispatcher調(diào)度器中有些什么?
[圖片上傳失敗...(image-10e627-1536636467207)]

可以知道诈火,它有三個雙端隊列兽赁,

// 雙端隊列,支持首尾兩端 雙向開口可進可出
    
    /**
     * 準備運行的異步隊列
     * 
     */
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

    // 正在運行的異步
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

    // 正在執(zhí)行的同步隊列
    private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

隊列中線程使用線程池:

    /**
     * 線程池的方式啟動線程冷守,使用懶加載的方式
     */
    private @Nullable ExecutorService executorService;
    
    public synchronized ExecutorService executorService() {
        if (executorService == null) {
            //TODO 線程池
            //TODO 核心線程 最大線程 非核心線程閑置60秒回收 任務(wù)隊列
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",
                    false));
        }
        return executorService;
    }

而上面client執(zhí)行調(diào)度器中的excute方法刀崖,實際上就是將當前請求直接添加到這個同步的雙端隊列中,等待線程池中的隊列被執(zhí)行拍摇!

—— getResponseWithInterceptorChain()

接下來就要執(zhí)行攔截器了亮钦,而攔截器中就是使用了我們今天所知道的責任鏈模式,上面的責任鏈模式已經(jīng)說的很清晰了充活,一環(huán)接著一環(huán)蜂莉,一個對象持有下個對象的引用;我們看OKHttp中的責任鏈模式是怎樣寫的混卵,點擊進入該方法:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //責任鏈 實際上是像遞歸一樣倒敘執(zhí)行
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    //5映穗、重試與重定向
    interceptors.add(retryAndFollowUpInterceptor);
    // 4、請求頭等信息
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //3幕随、緩存配置 根據(jù)條件(存在響應緩存并被設(shè)置為不變的或者響應在有效期內(nèi))返回緩存響應設(shè)置請求頭(If-None-Match蚁滋、If-Modified-Since等) 服務(wù)器可能返回304(未修改)
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //2、連接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //1合陵、流操作(寫出請求體枢赔、獲得響應數(shù)據(jù))進行http請求報文的封裝與請求報文的解析
    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);
  }

可以看到,getResponseWithInterceptorChain() 方法拥知,是將5個攔截器添加到鏈表中,再新建了一個RealInterceptorChain.class 類碎赢,然后執(zhí)行了我們責任鏈中抽象處理類的處理方法 proceed低剔,這里是使用了接口的形式:

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    @Nullable Connection connection();

    Call call();

    int connectTimeoutMillis();

    Chain withConnectTimeout(int timeout, TimeUnit unit);

    int readTimeoutMillis();

    Chain withReadTimeout(int timeout, TimeUnit unit);

    int writeTimeoutMillis();

    Chain withWriteTimeout(int timeout, TimeUnit unit);
  }
}

所以,責任現(xiàn)在都交給了RealInterceptorChain, 上面直接調(diào)用了Interceptor.Chain接口中的 proceed方法襟齿,我們看下他的實現(xiàn):

 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
      
      .....

    // 創(chuàng)建新的攔截鏈姻锁,鏈中的攔截器集合index+1
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //執(zhí)行當前的攔截器 默認是:retryAndFollowUpInterceptor
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    ......
    
    return response;
  }

主要是上面三行代碼,首先拿到下一個 攔截器猜欺,上面添加的第一個攔截器是 retryAndFollowUpInterceptor (重試與重定向)攔截器位隶,然后將下一個攔截器傳入到重試與重定向攔截器中,看看intercept這個方法在實現(xiàn)類中做的操作:

@Override public Response intercept(Chain chain) throws IOException {

    Request request = chain.request();
    // 首先拿到當前真實的Interceptor 實現(xiàn)類
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    // 核心 協(xié)調(diào)連接开皿、請求/響應以及復用
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        //執(zhí)行到一半涧黄,又去執(zhí)行了RealInterceptorChain中的proceed方法
        //實際上就是下一個攔截器
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        .....
      }
   }

這個過程其實就是遞歸的過程,而底就是CallServerInterceptor 赋荆,這里不對攔截器作詳細的講解笋妥,每個攔截器做的處理邏輯都差不多,下面我們看下這個過程的圖解:

img

OKHttp中的責任鏈.jpg

總結(jié):

OKHttp中使用攔截器對不同的業(yè)務(wù)進行區(qū)分窄潭,我們也可以使用自己的自定義攔截器
其中的責任鏈模式其實和我們設(shè)計模式中的有區(qū)別春宣,這里是將分發(fā)處理給了接口,讓其去處理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嫉你,一起剝皮案震驚了整個濱河市月帝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幽污,老刑警劉巖嫁赏,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異油挥,居然都是意外死亡潦蝇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門深寥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來攘乒,“玉大人,你說我怎么就攤上這事惋鹅≡蛟停” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵闰集,是天一觀的道長沽讹。 經(jīng)常有香客問我,道長武鲁,這世上最難降的妖魔是什么爽雄? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮沐鼠,結(jié)果婚禮上挚瘟,老公的妹妹穿的比我還像新娘叹谁。我一直安慰自己乘盖,他們只是感情好焰檩,可當我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著订框,像睡著了一般析苫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上穿扳,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天衩侥,我揣著相機與錄音,去河邊找鬼纵揍。 笑死顿乒,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的泽谨。 我是一名探鬼主播璧榄,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吧雹!你這毒婦竟也來了骨杂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤雄卷,失蹤者是張志新(化名)和其女友劉穎搓蚪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丁鹉,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡妒潭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了揣钦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雳灾。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖冯凹,靈堂內(nèi)的尸體忽然破棺而出谎亩,到底是詐尸還是另有隱情,我是刑警寧澤宇姚,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布匈庭,位于F島的核電站,受9級特大地震影響浑劳,放射性物質(zhì)發(fā)生泄漏阱持。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一呀洲、第九天 我趴在偏房一處隱蔽的房頂上張望紊选。 院中可真熱鬧啼止,春花似錦道逗、人聲如沸兵罢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卖词。三九已至,卻和暖如春吏夯,著一層夾襖步出監(jiān)牢的瞬間此蜈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工噪生, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留裆赵,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓跺嗽,卻偏偏與公主長得像战授,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子桨嫁,可洞房花燭夜當晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容