OkHttp源码中的建造者模式之所以有必要单独拿出来讲,是因为OkHttp 3.x和4.x分别用Java语言和Kotlin语言写的,所以需要做一个对比分析。

在OkHttp的源码中搜索“Builder”,可以看到OkHttp的OkHttpClient、Request和Response等很多类的代码里包含名为Builder的子类,这些都是建造者模式的应用。


OkHttpClient.Builder

OkHttpClient是一个内部及其复杂的类,内部包含一系列超时时间(Timeout),代理(proxy),缓存(cache),分发器(dispatcher),拦截器(interceptors)等等。OkHttpClient.Builder是建造者模式的典型应用。

以下是3.x的实现:

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
  // ……代码省略……

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

  public static final class Builder {
    // ……代码省略……

    public Builder() {
      dispatcher = new Dispatcher();
      // ……代码省略……
      pingInterval = 0;
    }

    Builder(OkHttpClient okHttpClient) {
      this.dispatcher = okHttpClient.dispatcher;
      // ……代码省略……
      this.pingInterval = okHttpClient.pingInterval;
    }

    //
    public Builder callTimeout(long timeout, TimeUnit unit) {
      callTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }

    // ……代码省略……

    public Builder addInterceptor(Interceptor interceptor) {
      if (interceptor == null) throw new IllegalArgumentException("interceptor == null");
      interceptors.add(interceptor);
      return this;
    }

    public Builder addNetworkInterceptor(Interceptor interceptor) {
      if (interceptor == null) throw new IllegalArgumentException("interceptor == null");
      networkInterceptors.add(interceptor);
      return this;
    }

    public OkHttpClient build() {
      return new OkHttpClient(this);
    }
  }
}

以下是4.x的实现

open class OkHttpClient internal constructor(
  builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {

  constructor() : this(Builder())
  
  class Builder constructor() {
    internal var dispatcher: Dispatcher = Dispatcher()
    // ……代码省略……
    internal var routeDatabase: RouteDatabase? = null

    internal constructor(okHttpClient: OkHttpClient) : this() {
      this.dispatcher = okHttpClient.dispatcher
      // ……代码省略……
      this.routeDatabase = okHttpClient.routeDatabase
    }

    fun addInterceptor(interceptor: Interceptor) = apply {
      interceptors += interceptor
    }

    fun addNetworkInterceptor(interceptor: Interceptor) = apply {
      networkInterceptors += interceptor
    }

    // ……代码省略……
    
    fun cookieJar(cookieJar: CookieJar) = apply {
      this.cookieJar = cookieJar
    }

    @IgnoreJRERequirement
    fun readTimeout(duration: Duration) = apply {
      readTimeout(duration.toMillis(), MILLISECONDS)
    }

    @IgnoreJRERequirement
    fun writeTimeout(duration: Duration) = apply {
      writeTimeout(duration.toMillis(), MILLISECONDS)
    }

    fun build(): OkHttpClient = OkHttpClient(this)
  }

}


Request.Builder

Request是发送出去的请求,与Response相对。

以下是3.x的实现:

public final class Request {

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
  }

  // ……代码省略……

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

  public static class Builder {
    @Nullable HttpUrl url;
    String method;
    Headers.Builder headers;
    @Nullable RequestBody body;

    Map<Class<?>, Object> tags = Collections.emptyMap();

    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      this.body = request.body;
      this.tags = request.tags.isEmpty()
          ? Collections.emptyMap()
          : new LinkedHashMap<>(request.tags);
      this.headers = request.headers.newBuilder();
    }

    public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      // Silently replace web socket URLs with HTTP URLs.
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }

      return url(HttpUrl.get(url));
    }

    // ……代码省略……
    
    public Builder get() {
      return method("GET", null);
    }

    public Builder head() {
      return method("HEAD", null);
    }

    public Builder post(RequestBody body) {
      return method("POST", body);
    }

    public Builder tag(@Nullable Object tag) {
      return tag(Object.class, tag);
    }

    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
  }
}

以下是4.x的实现:

class Request internal constructor(
  @get:JvmName("url") val url: HttpUrl,
  @get:JvmName("method") val method: String,
  @get:JvmName("headers") val headers: Headers,
  @get:JvmName("body") val body: RequestBody?,
  internal val tags: Map<Class<*>, Any>
) {

  // ……代码省略……

  fun newBuilder(): Builder = Builder(this)

  // ……代码省略……

  open class Builder {
    internal var url: HttpUrl? = null
    internal var method: String
    internal var headers: Headers.Builder
    internal var body: RequestBody? = null

    /** A mutable map of tags, or an immutable empty map if we don't have any. */
    internal var tags: MutableMap<Class<*>, Any> = mutableMapOf()

    constructor() {
      this.method = "GET"
      this.headers = Headers.Builder()
    }

    internal constructor(request: Request) {
      this.url = request.url
      this.method = request.method
      this.body = request.body
      this.tags = if (request.tags.isEmpty()) {
        mutableMapOf()
      } else {
        request.tags.toMutableMap()
      }
      this.headers = request.headers.newBuilder()
    }

    open fun url(url: HttpUrl): Builder = apply {
      this.url = url
    }

    open fun url(url: String): Builder {
      // Silently replace web socket URLs with HTTP URLs.
      val finalUrl: String = when {
        url.startsWith("ws:", ignoreCase = true) -> {
          "http:${url.substring(3)}"
        }
        url.startsWith("wss:", ignoreCase = true) -> {
          "https:${url.substring(4)}"
        }
        else -> url
      }

      return url(finalUrl.toHttpUrl())
    }

    open fun url(url: URL) = url(url.toString().toHttpUrl())

    // ……代码省略……

    open fun get() = method("GET", null)

    open fun head() = method("HEAD", null)

    open fun post(body: RequestBody) = method("POST", body)

    open fun method(method: String, body: RequestBody?): Builder = apply {
      require(method.isNotEmpty()) {
        "method.isEmpty() == true"
      }
      if (body == null) {
        require(!HttpMethod.requiresRequestBody(method)) {
          "method $method must have a request body."
        }
      } else {
        require(HttpMethod.permitsRequestBody(method)) {
          "method $method must not have a request body."
        }
      }
      this.method = method
      this.body = body
    }

    // ……代码省略……

    open fun build(): Request {
      return Request(
          checkNotNull(url) { "url == null" },
          method,
          headers.build(),
          body,
          tags.toImmutableMap()
      )
    }
  }
}

用到了apply()函数


建造者模式在OkHttp源码中的应用极其广泛,除了以上提到的之外,还有Cookie.Builder、Response.Builder、FormBody.Builder等用到了建造者模式,本文不做过多讨论。