美文网首页
Builder模式演义(2)——OkHttp源码中的Builde

Builder模式演义(2)——OkHttp源码中的Builde

作者: 脐橙熟了 | 来源:发表于2017-03-17 01:50 被阅读0次

    引言

    在上一篇Builder模式演义(1)中介绍了Builder模式的标准形式,以及两种基本变换——链式调用和省略指挥者角色。本文将通过分析OkHttp源码阐述Builder模式的另外两种变换——省略抽象Builder角色和Product角色回炉再造。

    OkHttp源码中的Builder模式

    OkHttp作为开源的Android网络请求框架,以URLConnection和HttpClient的替代者身份出现,名噪江湖。许多开源框架都是基于OkHttp的二次封装,比如OkGo,以及与OkHttp同源的Retrofit。OkHttp的使用非常简单,在Github上OkHttp项目的Wiki/Recipes中有基本的介绍。

    private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
        Response response = client.newCall(request).execute();
    //省略其他代码
    }
    

    由上述代码的调用风格我们可以基本猜到,Request对象的构建采用了Builder模式。为了验证这一点,我们看看源码中Request类的具体实现。

    public final class Request {
      final HttpUrl url;
      final String method;
      final Headers headers;
      final RequestBody body;
      final Object tag;
    
      private volatile CacheControl cacheControl; // Lazily initialized.
    
      Request(Builder builder) {
        //构造函数,省略属性赋值操作
      }
    
      public Builder newBuilder() {
        return new Builder(this);
      }
    //省略部分代码
      public static class Builder {
        HttpUrl url;
        String method;
        Headers.Builder headers;
        RequestBody body;
        Object tag;
    //省略部分代码
        Builder(Request request) {
          //构造函数,省略属性赋值操作
        }
    //省略部分代码
         public Request build() {
          if (url == null) throw new IllegalStateException("url == null");
             return new Request(this);
        }
    }
    

    上述代码非常清晰,Request类共有6个属性,从名字就可以猜出它们的意义:url代表这个Http请求的url,method指的是POST请求还是GET请求还是其他,header和body自然就是http请求的首部和请求体;tag猜测是用作取消一个请求,cacheControl是缓存控制。Request.Builder中冗余定义了这6个属性。builder在经过一系列的链式调用,对这6个属性中的某几个进行赋值后,最终调用build()方法,生成一个完整的Request对象。build()方法的具体实现也很简单,调用Request的构造方法,将自己作为参数传递过去。Request构造方法内对6个属性一一赋值(没有值时使用默认值)。

    省略抽象Builder角色

    仔细研究,发现Request类的6个属性分别对应的类型中,Headers、CacheControl、HttpUrl类的内部都含有Builder!RequestBody是个抽象类,其内部没有Builder,但是它的两个子类FormBody和MultipartBody有。于是画出如下UML类图。

    OkHttp源码中使用Builder模式的部分类
      对照上一篇Builder模式演义(1)中GoF标准Builder模式,原本我们很希望RequestBody中有一个Builder作为抽象Builder角色,作为FormBody.Builder和MultipartBody.Builder的共同父类。然而Request.Builder根本不存在!上图中除了RequestBody,其他每个类中的Builder都是独立存在的,除宿主类(Builder所在的外部类),不和其他类发生任何牵扯。
      换一种方式理解,如果Builder模式中的ConcreteBuilder只有一个,那么抽象的Builder当然可以省略。此所谓Builder模式变换之省略抽象Builder角色

    Product角色回炉再造

    省略指挥者角色,省略抽象Builder角色,整个Builder模式只剩下两个角色,如下图。


    省略指挥者和抽象Builder之后的Builder模式

      相信你已经非常熟悉Builder模式的使用套路了——在经过一系列的链式调用对属性进行赋值后,ConcreteBuilder最终调用build()方法生成Product对象。一旦调用build()方法,无法再设置或修改属性值了,因为build()返回的是Product类型,而不再是Builder本身。这本身是一种保护机制,也是Builder模式的特性。这好比打包邮寄东西,一旦封包,无法再继续往里面塞,更无法在运输的途中,进行远程遥控替换里面的某件物品。
      然而凡事无绝对,设想这样一种场景:假如上述的Request类中的属性数不是6而是30,通过长长的链式调用,我配置了其中的20个属性,一声令下调用build()方法获取到了一个request1对象,并一直在使用着;在某个特殊的场景下,我需要使用和request1基本相同的配置,只有两个属性值不同。这时候该如何去获得request1对象的一个拷贝,然后设置那两个不同的属性值呢?想到两种方式:

    1. 让Request实现Cloneable接口,或者仿照C++实现一个形如Request(Request other){...}的拷贝构造函数。
    2. 将request1对象序列化后再反序列化,得到另一个对象request2,它和request1所有属性都相同。

    方式1存在着深拷贝、浅拷贝的问题,再者,为每个复杂对象实现Clonable接口或拷贝构造函数工作量巨大而且非常难以维护;方式2存在着空间和性能的开销。Builder模式的问题,有它自己的解决逻辑!从Builder到Product并不一定是单向不可逆的过程。回看文章开头Request类的源码,有一个newBuilder()方法,它返回Request.Builder()类型。newBuilder()的内部,调用的是Builder的一个含Request类型参数的构造函数。

    public Builder newBuilder() {
        return new Builder(this);
      }
    

    Request(Builder builder){...}和Builder(Request request) {...},外部类和内部类的构造函数,是否有种对称美?正是这种美,巧妙地完成了从Product重回Builder的逆向过程。再接下来的事,就是继续链式调用最后调用build()模式一锤定音。至此,Builder模式Builder和Product的关系如下。


    Product和Builder角色的相互转换

    OkHttp官网解释回炉再造

    其实在Github上OkHttp项目的Wiki/Recipes中,有这样一段话:

    Per-call Configuration
    All the HTTP client configuration lives in OkHttpClient
    including proxy settings, timeouts, and caches. When you need to change the configuration of a single call, call OkHttpClient.newBuilder()
    . This returns a builder that shares the same connection pool, dispatcher, and configuration with the original client. In the example below, we make one request with a 500 ms timeout and another with a 3000 ms timeout.

    大体意思是,所有HTTP请求都使用全局的OKHttpClient配置,包括代理、超时、缓存等。如果要为某一两个单独的请求修改配置,就调用OkHttpClient.newBuilder()。它返回的OkHttpClient.Builder和原先那个全局的有一样的连接池、分发器和配置。然后就是示例代码,如下。

    private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
            .build();
    
        try {
          // Copy to customize OkHttp for this request.
          OkHttpClient copy = client.newBuilder()
              .readTimeout(500, TimeUnit.MILLISECONDS)
              .build();
    
          Response response = copy.newCall(request).execute();
          System.out.println("Response 1 succeeded: " + response);
        } catch (IOException e) {
          System.out.println("Response 1 failed: " + e);
        }
    
        try {
          // Copy to customize OkHttp for this request.
          OkHttpClient copy = client.newBuilder()
              .readTimeout(3000, TimeUnit.MILLISECONDS)
              .build();
    
          Response response = copy.newCall(request).execute();
          System.out.println("Response 2 succeeded: " + response);
        } catch (IOException e) {
          System.out.println("Response 2 failed: " + e);
        }
      }
    

    对,你没有看错,这不是Request.newBuilder()的使用,而是OKHttpClient.newBuilder()。OKHttpClient类也使用Builer模式!具体的实现请自行查看源码了。

    总结

    至此,已经介绍了Builder模式的四种变换。

    1. 链式调用:
        并非Builder模式特有,只要在原本返回值为void的方法中返回this,都可以实现链式调用。
    2. 省略指挥者角色:
        new builder、链式赋值、最后build,一条龙调用,不再需要指挥者角色。
    3. 省略抽象Builder角色:
        具体的Builder只有一个,省略抽象父类。
    4. Product角色的回炉再造:
        Product逆转化为Builder,调整某些配置后,重新build,回到Product形态。

    Builder设计模式使用如此广泛,又如此灵活。我们在实际开发特别是重构、封装时,可适当借鉴,定能更上一层逼格。有时间可以再挖一挖OkGo和Retrofit中的Builder模式,理解会更加深刻,使用会更加得心应手。

    相关文章

      网友评论

          本文标题:Builder模式演义(2)——OkHttp源码中的Builde

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