InterceptorChain模式

最近在看OkHttp源碼時,看到了Interceptor的使用,覺得還是很巧妙的設計,所以就提煉出來,已被不時之需。暫且就稱為InterceptorChain模式吧。

先來看看OkHttp中InterceptorChain的使用場景吧。

Activity InterceptorChain-1.jpg

OkHttp的每個網絡請求都需要創建一個Request,最后返回一個Response。但并不是簡單地請求-響應過程,在這其中還有多個Interceptor參與其中。如上圖所示,Request依次經過Interceptor1,Interceptor2...(如果有多個Interceptor),最后到達Inner Interceptor。Inner Interceptor創建一個Response,然后再原路返回。在這個過程中,每一個Interceptor都可以對Request和Response做操作。這些Interceptor就如同一個個鐵環般連接在一個形成一個鏈條,這就是InterceptorChain名字的由來。
最簡單的一個例子就是LogInterceptor,它只是簡單的打印Request和Response中的內容。
怎么才能組成這樣一個執行鏈條呢?OkHttp使用了List保存Interceptor,然后再象遞歸一樣,一個個的從中取出Intercepor執行,這樣的一個過程會在后面的例子中看到。

在什么場景下使用這個模式呢?

如果你需要實現一個類似請求-響應的功能,并且你想對請求體或響應結果做一些處理,但是又不想在這個業務邏輯中摻入不相關的邏輯(比如打印日志,數據分析),那么你就可以使用這個模式。當然你還可以想出其他的方式來使用它。下面就來看看具體的例子吧。
場景:圖片的讀取
這是我假設的一個場景。假設,我需要寫一個圖片加載器,它最基本的作用就是傳入圖片在磁盤中的地址,然后讀取圖片文件并返回原始圖片。那么剛開始的代碼可能像下面這樣:
<pre>
public Image load(String filePath) {
File imageFile = new File(filePath);
//File to Image
....
return image
}
</pre>

之后的某一天你需要記錄一下獲取的圖片名稱,然后你的代碼就變成了這樣:
<pre>
public Image load(String filePath) {

System.out.println("image file:" + filePath);
File imageFile = new File(filePath);

//File to Image
....

return image

}
</pre>
這看起來并不很糟。

又過了一段時間,又需要記錄下圖片文件被訪問的次數。嗯,好吧,還是在原來的方法上直接修改:
<pre>
public Image load(String filePath) {

    System.out.println("image file:" + filePath);
    
    recordCount();

    File imageFile = new File(filePath);
    //File to Image
    ....
    return image

}
</pre>

好吧,暫時這樣吧。

再然后,來了一個新需求:讀取完圖片后需要對圖片進行放縮,返回放縮后的圖片。這時候怎么辦呢?還是在load方法中增加縮放的邏輯?不太好吧,有的地方又不需要縮放。另外寫一個縮放方法?好像可以,不過需要在所有調用load方法的地方增加一條調用縮放方法的代碼,有點麻煩哦。怎么辦呢?
這時候你可能就需要重構代碼了。說到這里就該InterceptorChain登場了。

設想一下,你可以有一個最基本的讀取圖片的方法,其他的諸如日志、記錄訪問次數、縮放功能都能隨意配置,這是不是很爽。
下面我們就看看怎么才能達到這個目的。

整體結構:

class diagram.png

我們把加載圖片的邏輯提了出來,封裝在一個單獨的類:ImageLoader中。ImageLoader中用List保存Interceptor,load方法入參是Request,返回Response。請求的圖片地址保存在Request中,讀取到的圖片內容保存在Response中。Interceptor的intercept方法傳入Chain,Chain有個接口request,用于返回Request。Chain還有個接口proceed,用于處理Request。整體的思路是ImageLoader調用load方法,load方法里創建一個ChainAdapter,ChainAdapter用于封裝Interceptor的執行。在ChainAdapter的proceed方法中執行Interceptor的intercept方法,然后取出下一個Interceptor,進行封裝。這樣就形成了一個鏈條。這樣說還是很抽象的,下面就上代碼。

先來看看ImageLoader :
<pre>
public class ImageLoader {

private List<Interceptor> interceptors = new LinkedList<>();
private final IOInterceptor io = new IOInterceptor();

private Request request;

public void setRequest(Request request) {
    this.request = request;
}

public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
}

public Response load() {
    interceptors.add(io);
    
    ChainAdapter chain = new ChainAdapter(interceptors, 0, request);
    
    return chain.proceed(request);
}

private class IOInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) {
        
        final Request request = chain.request();
        
        return getImageFromFile(request.fileName);
    }
    
    private Response getImageFromFile(String fileName) {
        Response respose = new Response();
        
        String[] strs = fileName.split("\\\\");
        
        respose.setImage(strs[strs.length - 1]);
        
        return respose;
    }

}

}
</pre>

我們看到load方法中有一句:
<pre>
interceptors.add(io);
</pre>

這一句很關鍵哦,io是內部類IOInterceptor,外部是不能創建它的,它的intercept方法有點特殊,后面我們會講到。在interceptors.add(io)之后是
<pre>
ChainAdapter chain = new ChainAdapter(interceptors, 0, request);
return chain.proceed(request);
</pre>
形成鏈的奧秘都在ChainAdapter里,它實現了Chain接口,看完Interceptor和Chain接口我們再回來說說它。

再看Interceptor這個接口:
<pre>
public interface Interceptor {

Response intercept(Chain chain);

  interface Chain {
    Request request();

    Response proceed(Request request);
  }

}
</pre>
很簡單,沒什么好說的。下面就進入ChainAdapter了
<pre>
public class ChainAdapter implements Chain {

private final List<Interceptor> interceptors;
private final int index;
private final Request request;

public ChainAdapter(List<Interceptor> interceptors, int index, Request request) {
    this.index = index;
    this.request = request;
    this.interceptors = interceptors;
}

@Override
public Request request() {
    return request;
}

@Override
public Response proceed(Request request) {
    ChainAdapter next = new ChainAdapter(interceptors, index + 1, request);
    
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    
    return response;
}

}
</pre>

來看看proceed方法。首先是取出下一個Interceptor,然后也封裝成ChainAtapter。接著從List中取出當前的Interceptor,調用它的intercept方法,參數傳入的就是之前封裝好的ChainAtapter。好了,到現在調用鏈條形成了。等等,好像哪里不對勁?next的proceed方法并沒有被調用,鏈條斷了!說到這就需要為這個模式引入一個約定了。那就是在Interceptor的intercept方法中,程序員要自行調用作為參數傳入的Chain的proceed方法(這是我覺得這個模式不夠爽的一點)。
程序員在調用了proceed方法后會怎么樣呢?通過之前的代碼我們看到,每一個Interceptor都被封裝到ChainAdapter里,那么intercept方法中的Chain實際就只能是這個ChainAdapter,ChainAdapter的proceed方法又封裝下一個Interceptor ,然后調用intercept...遞歸調用形成了,直到某個Interceptor的intercept方法中沒有調用Chain的proceed方法為止。不是說好了,程序員都要遵守在intercept中調用Chain的proceed方法這一約定嗎!對的,只有一個Interceptor 可以不遵守這個約定,那就是之前提到的IOInterceptor。下面我們就來看看IOInterceptor:
<pre>
private class IOInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) {
final Request request = chain.request();
return getImageFromFile(request.fileName);
}
private Response getImageFromFile(String fileName) {
Response respose = new Response();
String[] strs = fileName.split("\");
respose.setImage(strs[strs.length - 1]);
return respose;
}
}
</pre>
它是個私有類,所以并沒有違反約定,程序員不知道它的存在。這個類也很簡單,沒什么好說的了。再來看看我寫的LogInterceptor。拿它和IOInterceptor 做個對比。
<pre>
public class LogInterceptor implements Interceptor {

@Override
public Response intercept(Chain chain) {
    
    final Request request = chain.request();
    
    System.out.println("Request image file:" + request.fileName);
    
    final Response response = chain.proceed(request);
    
    System.out.println(request.fileName + " size:" + response.getImageSize());
    
    return response;
}

}
</pre>
在intercept方法中,首先從chain中取出Request進行打印,然后按約定調用proceed方法,接著再打印proceed方法返回的Response,最后返回Response?;仡^去看IOInterceptor的intercept方法,它沒有調用chain.proceed,因為它是鏈條的最后一環,所以是時候結束了。
到這里InterceptorChain模式的整個運行過程就講完了。最后來模擬一下ImageLoader的使用。
<pre>
Request request = new Request();

request.fileName = "images" + File.separator + "icon.png";

ImageLoader loader = new ImageLoader();
loader.setRequest(request);

loader.addInterceptor(new LogInterceptor());
loader.addInterceptor(new CountInterceptor());

Response response = loader.load();

// Do some thing for response
System.out.println("handle " + response.getImage());
</pre>

每個Interceptor各司其職,還能靈活的組合使用,是不是很不錯。

(有需要源碼的請留言。java代碼)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 緩存的一般思路 下面是我理解的網絡請求框架的緩存基本實現。大致的過程是有緩存用緩存的數據,沒緩存發起http請求取...
    原件閱讀 2,826評論 3 12
  • 關于okhttp是一款優秀的網絡請求框架,關于它的源碼分析文章有很多,這里分享我在學習過程中讀到的感覺比較好的文章...
    蕉下孤客閱讀 3,625評論 2 38
  • 這篇文章主要講 Android 網絡請求時所使用到的各個請求庫的關系,以及 OkHttp3 的介紹。(如理解有誤,...
    小莊bb閱讀 1,226評論 0 4
  • 簡介 目前在HTTP協議請求庫中,OKHttp應當是非?;鸬?,使用也非常的簡單。網上有很多文章寫了關于OkHttp...
    第八區閱讀 1,397評論 1 5
  • 1.陽光 遙望著海南島的丘陵,白云黑山看著也不賴。 光在這里是某種生活必需品,刺痛感強烈,卻可以烘干入夜潮濕的影子...
    lesley氤閱讀 400評論 0 1