美文网首页程序员
InterceptorChain模式

InterceptorChain模式

作者: Rocky1982 | 来源:发表于2017-03-23 14:12 被阅读302次

最近在看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代码)

相关文章

  • InterceptorChain模式

    最近在看OkHttp源码时,看到了Interceptor的使用,觉得还是很巧妙的设计,所以就提炼出来,已被不时之需...

  • sourceCode

    拦截器 org.apache.ibatis.plugin.InterceptorChain 参数解析的地方 co...

  • 6. interceptorChain

    mybatis 的interceptorChain是给扩展用的, 常用的比如分页扩展插件. mybatis提供的r...

  • OkHttp3源码学习之InterceptorChain

    介绍 拦截器链,采用责任链模式,将一次事物的耦合度降低。 源码分析 RealInterceptorChain Re...

  • JS 设计模式

    工厂模式 单体模式 模块模式 代理模式 职责链模式 命令模式 模板方法模式 策略模式 发布-订阅模式 中介者模式 ...

  • iOS设计模式

    设计模式:MVC模式、单例模式、观察者模式、工厂模式、代理模式、策略模式、适配器模式、模版模式、外观模式、创建模式...

  • iOS知识点总结

    一、设计模式:MVC模式、单例模式、观察者模式、MVVM模式、工厂模式、代理模式、策略模式、适配器模式、模板模式、...

  • 常用设计模式 2018-09-15

    目录 代理模式 单例模式 命令模式 工厂模式 桥接模式 策略模式 装饰模式 观察者模式 门面模式 代理模式 静态代...

  • 计算机等级考试三级数据库复习(五)

    1.数据库系统 三级模式——》内模式,模式,外模式 二级映像模式(外模式/模式,模式/内模式) 模式/内模式提供数...

  • Retrofit源码解析

    设计模式:建造者模式、工厂方法模式、外观模式、代理模式、单例模式、策略模式、装饰模式、适配器模式、代理模式 一、R...

网友评论

    本文标题:InterceptorChain模式

    本文链接:https://www.haomeiwen.com/subject/nxaonttx.html