美文网首页程序员
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模式

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