首页 > 编程语言 >【设计模式】建造者模式——OkHttp源码中的建造者模式

【设计模式】建造者模式——OkHttp源码中的建造者模式

时间:2024-02-07 18:31:22浏览次数:32  
标签:body null url Builder 建造 public 源码 设计模式 method

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等用到了建造者模式,本文不做过多讨论。




标签:body,null,url,Builder,建造,public,源码,设计模式,method
From: https://blog.51cto.com/dongfeng9ge/9639539

相关文章

  • 扒开源安卓性能测试工具moblieperf源码——开发属于你自己的性能稳定性测试工具
    moblieperf下载和使用moblieperf由阿里巴巴开源的Android性能测试工具下载:官方源码地址mobileperfgithub使用:使用pycharm打开下载的项目使用只需要修改配置文件config.conf即可运行采集:a.mac、linux在mobileperf工具根目录下执行shrun.sh;b.windows双击run.bat配置......
  • 源码搭建教学:直播带货商城小程序开发
    结合小程序开发的直播带货商城,不仅可以提供更便捷的购物体验,还可以实现更高效的销售。因此,学习如何搭建一个直播带货商城小程序将成为您拓展商业领域的利器。步骤一:准备工作在开始开发之前,您需要进行一些准备工作。首先,确保您已经安装了微信开发者工具,并且注册了微信小程序的开发者......
  • 设计模式:适配器模式
    设计模式是通用的、可复用的代码设计方案,也可以说是针对某类问题的解决方案,因此,掌握好设计模式,可以帮助我们编写更健壮的代码。wiki中将设计模式分为四类,分别是:创建模式(creationalpatterns)结构模式(structuralpatterns)行为模式(behavioralpatterns)并发模式(concurrencypatt......
  • 二刷 K8s 源码 - workqueue 的所有细节
    1.概述-何来此文2.Queue的实现2.1Queue.Add(iteminterface{})方法2.2Queue.Get()方法2.3Queue.Done(iteminterface{})方法3.DelayingQueue的实现4.RateLimitingQueue的实现5.rateLimiter限速器的实现6.控制器里用的默认限速器7.总结1.概述-......
  • [Ngbatis源码学习] Ngbatis 源码学习之资源加载器 DaoResourceLoader
    Ngbatis源码阅读之资源加载器DaoResourceLoaderDaoResourceLoader是Ngbatis的资源文件加载器,扩展自MapperResourceLoader。本篇文章主要分析这两个类。1.相关类MapperResourceLoaderDaoResourceLoader2.MapperResourceLoader在介绍DaoResourceLoader之前有必要......
  • 通达信跟庄乾坤源码副图
    {股票指标}DLYZ1:=SUM((WINNER(C)*100),30)/30*0.1;DLYZ2:=SUM((WINNER(C)*100),20)/20*0.4;DLYZ3:=SUM((WINNER(C)*100),10)/10*0.3;DLYZ4:=SUM((WINNER(C)*100),5)/5*0.1;DLYZ5:=SUM((WINNER(C)*100),3)/3*0.1;DLYZZ:=DLYZ1+DLYZ2+DLYZ3+DLYZ4+DLYZ5;赚钱效应:IF(DLYZ......
  • 通达信暗流涌动指标公式源码副图
    {股票指标}TYP:=(IF(HIGH<=0,CLOSE,HIGH)+IF(LOW<=0,CLOSE,LOW)+CLOSE)/3;CL:=(TYP-ma(TYP,30))/(0.015*AVEDEV(TYP,30));C2:=MA(CL,4);C3:=MA(CL,10);DRAWBAND(CL,RGB(0,255,0),C2,RGB(0,0,0));DRAWBAND(CL,RGB(255,0,0),C3,RGB(0,0,0));动力:CL,COLORYELLOW,LINET......
  • 通达信MACD买卖副图指标公式源码
    {股票指标}VAR3:=(CLOSE-MA(CLOSE,6))/MA(CLOSE,6)*100; VAR4:=(CLOSE-MA(CLOSE,24))/MA(CLOSE,24)*100;VAR5:=(CLOSE-MA(CLOSE,32))/MA(CLOSE,32)*100;VAR6:=(VAR3+VAR4+VAR5)/3;VAR7:=EMA(VAR6,5);指标:=EMA(EMA(VAR3,5),5)*3,COLORSTICK;VAR8:=IF(VAR6<=-20,10,0......
  • 通达信【龙头战狼】珍藏版套装指标 止盈止损 胜者为王 终极盈利模式 源码文件分享
    {股票指标}【战狼波段主图】本套指标设计两个主图,一个看盘简单明了,还有一个波段主图,操作波段上涨非常好,买卖点明确!【战狼主力资金】副图有拉升资金和主力资金两个信号,两个信号都大于0,而且处于上升趋势,易产生大妖股,可以辅助战狼信号操盘.1、指标原理:本指标主力洗盘+拉升特征......
  • 通达信鼎牛猎杀选股指标公式源码副图
    {股票指标}YM:=Ema(SLOPE((CLOSE+HIGH+LOW)/3,24)*20+(CLOSE+HIGH+LOW)/3,48);macd价格1:=EMA(CLOSE,3)-EMA(CLOSE,9);DEA1021:=EMA(EMA(MacD价格1,102)-EMA(MACD价格1,204),9);DEA511:=EMA(EMA(MACD价格1,51)-EMA(MACD价格1,102),9);DEA36361:=EMA(EMA(MACD价格1,36)-EMA......