OkHttp3中使用的設計模式


OkHttp3作爲一款最受歡迎的http網絡請求框架,具有極佳的可擴展性,非常值得我們去學習它的設計思想和模式。所爲他山之,石可以攻玉,不外如是。下面我們來看看它都是用了哪些好用的設計模式。

OkHttp中的責任鏈模式

OkHttp中最直接的責任鏈模式的使用就是Interceptor的使用。書寫簡單漂亮,使用也非常方便,只需要OkHttpClient.Builder調用addInterceptor()方法,將實現了Interceptor接口的類添加進去即可,擴展性和可定製化都非常方便。

OkHttpClient httpClient = new OkHttpClient.Builder()
                .addInterceptor(new HeaderInterceptor())
                .addInterceptor(new LogInterceptor())
                .addInterceptor(new HttpLoggingInterceptor(logger)
                ......
                .readTimeout(30, TimeUnit.SECONDS)
                .cache(cache)
                .build();

具體的可以參考我的博客:從OkHttp中學習設計模式—責任鏈模式

OkHttp中的建造者模式

OkHttp中最直接的建造者模式的使用就是XXBuilder的使用。在OkHttp中的OkHttpClientRequestResponseHttpUrlHeadersMultipartBody等大量使用了類似的建造者模式。

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
......
  public static final class Builder {
    Dispatcher dispatcher;
    @Nullable Proxy proxy;
    int callTimeout;
    int connectTimeout;
......

    public OkHttpClient build() {
      return new OkHttpClient(this);
    }
  }
}
public class Headers {
......
  public static final class Builder {
    final List<String> namesAndValues = new ArrayList<>(20);
......

    public Headers build() {
      return new Headers(this);
    }
  }
}
public class Request {
......

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

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

將對象的創建與表示相分離,Builder負責組裝各項配置參數,並且生成對象,目標對象則對外提供接口,符合類的單一原則
具體的可以參考我的博客:從OkHttp中學習設計模式–建造者模式

OkHttp中的工廠模式

OkHttp中的工廠模式的使用有CacheStrategy.Factory,這是一個簡單工廠模式,主要也是用於生成一個CacheStrategy對象。


public static class Factory {
    .........
  public static class Factory {
    final long nowMillis;
    final Request request;
    final Response cacheResponse;
    .........

    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;

      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
 	}
 	
    public CacheStrategy get() {
      CacheStrategy candidate = getCandidate();

      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        // We're forbidden from using the network and the cache is insufficient.
        return new CacheStrategy(null, null);
      }

      return candidate;
    }

    /** Returns a strategy to use assuming the request can use the network. */
    private CacheStrategy getCandidate() {
      // No cached response.
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      // Drop the cached response if it's missing a required handshake.
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      // If this response shouldn't have been stored, it should never be used
      // as a response source. This check should be redundant as long as the
      // persistence store is well-behaved and the rules are constant.
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }
		.......

      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        ......
        return new CacheStrategy(null, builder.build());
      }

      // Find a condition to add to the request. If the condition is satisfied, the response body
      // will not be transmitted.
      String conditionName;
      String conditionValue;
      if (etag != null) {
        .....
      } else {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }
	......
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }
    .........
  }
 }
}

工廠模式和建造者模式區別?

建造者模式和工廠模式都是用於生成一個對象,都屬於創建型的設計模式,但是他們的側重點是有所不同的。可以看到工廠模式的生成對象的過程更復雜,側重於對象的生成過程,而建造者模式更側重於各個參數配置的組合,生成過程反而不那麼複雜。

OkHttp中的門面模式

我看到網上有人分析,OkHttp中也存在門面模式(Okhttp3 總結研究 (面試)),這個模式可能太常見了,確實式一個很容易讓人忽略的地方,因爲幾乎所有框架或者開源庫基本上都或多或少的使用到了門面模式。
門面模式的定義:提供一個統一的接口去訪問多個子系統的多個不同的接口,它爲子系統中的一組接口提供一個統一的高層接口。也稱外觀模式。
簡單說就是提供一個類提供一些接口供客戶端使用,隱藏內部調用的複雜性。OkHttp中提供了一個類OkHttpClient來供用戶使用,其內部關聯了大量的對象,以及處理細節,但是用戶只需要根據OkHttpClient提供的接口使用即可。從這方面看確實大部分的開源框架都在使用門面模式。

OkHttp中存在監聽者模式嗎?

我看到網上有人分析,OkHttp中也存在監聽者模式EventListenerWebSocketListener,如果簡單的回調監聽也算監聽者模式的話,那就算,不過個人感覺都沒用到監聽者模式的精髓,沒法做到一處修改多處響應,只是有一個監聽者,簡單的回調監聽,對於後期需要其他地方的監聽回調來說並不方便擴展。

OkHttp中存在策略模式嗎?

最近在網上看到有網友說OkHttpCacheInterceptor有用到策略模式(Okhttp3 總結研究 (面試)),我看來下源碼OkHttp-3.12.0,沒法現有策略模式的使用痕跡,if-else倒是有不少,屬於可以改造成策略模式的地方。

public final class CacheInterceptor implements Interceptor {
  ......

  @Override public Response intercept(Chain chain) throws IOException {
  ......

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
  ......

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
  ......
    }

    return response;
  }
}

我們可以看到CacheInterceptor的主要方法intercept()確實有一些if-else的寫法,算是不同情況的處理策略。但是我個人卻覺得這裏作者並沒有用策略模式來封裝它。首先我們來看看策略模式的基本定義:定義一系列的算法,把每一個算法封裝起來, 並且使它們可相互替換。 我們看到的卻是算法並沒有封裝起來,直接寫成我們熟悉的過程式編程if-ese,而我們使用策略模式一般是爲了以後擴展和修改方便,但是再回頭看看源碼,if-else的方式修改起來一定都不方便,而且需要直接修改原有代碼,並不符合開閉原則。
個人覺得作者之所以不用策略模式封裝算法,是覺得沒必要做整個緩存的策略做修改,既定的幾種情況都已經考慮,緩存策略不需要變來變去,規範了整個流程,同時不鼓勵用戶修改整個緩存流程,也就沒必要使用設計模式,避免造成過度設計。

由於個人水平可能有限,如果上述博文有不正確的地方,歡迎大家指正

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章