[关闭]
@linux1s1s 2017-01-09T12:12:52.000000Z 字数 6438 阅读 874

Base Time-Design Patterns-Build

Base 2017-01


栗子

我们不管Build模式是啥,如何使用,在什么场景下使用,我们先看看在Android客户端红遍半边天的第三方库是如何用Build的。

OKHttp

OkHttp构建网络Request:

  1. Request request = new Request.Builder().url(url).build();

Retrofit

Retrofit构建实例:

  1. Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build();

Fresco

Fresco构建DraweeController实例:

  1. DraweeController controller = Fresco.newDraweeControllerBuilder()
  2. .setUri(Uri.parse(GIF))
  3. .setAutoPlayAnimations(true)
  4. .build();

从上面的三个实例,我们发现,大红大紫的三方库居然都使用了build模式,所以我们本着认真学习的姿势来看看他们是如何干活的。

OKHttp

我们准备以Okhttp为样板,深究内部的实现,其他两个大同小异,不再赘述。

涉及到Build的Request类如下:
Request.java

  1. public final class Request {
  2. private final HttpUrl url;
  3. private final String method;
  4. private final Headers headers;
  5. private final RequestBody body;
  6. private final Object tag;
  7. private volatile CacheControl cacheControl; // Lazily initialized.
  8. private Request(Builder builder) {
  9. this.url = builder.url;
  10. this.method = builder.method;
  11. this.headers = builder.headers.build();
  12. this.body = builder.body;
  13. this.tag = builder.tag != null ? builder.tag : this;
  14. }
  15. public HttpUrl url() {
  16. return url;
  17. }
  18. public String method() {
  19. return method;
  20. }
  21. public Headers headers() {
  22. return headers;
  23. }
  24. public String header(String name) {
  25. return headers.get(name);
  26. }
  27. public List<String> headers(String name) {
  28. return headers.values(name);
  29. }
  30. public RequestBody body() {
  31. return body;
  32. }
  33. public Object tag() {
  34. return tag;
  35. }
  36. public Builder newBuilder() {
  37. return new Builder(this);
  38. }
  39. /**
  40. * Returns the cache control directives for this response. This is never null, even if this
  41. * response contains no {@code Cache-Control} header.
  42. */
  43. public CacheControl cacheControl() {
  44. CacheControl result = cacheControl;
  45. return result != null ? result : (cacheControl = CacheControl.parse(headers));
  46. }
  47. public boolean isHttps() {
  48. return url.isHttps();
  49. }
  50. @Override public String toString() {
  51. return "Request{method="
  52. + method
  53. + ", url="
  54. + url
  55. + ", tag="
  56. + (tag != this ? tag : null)
  57. + '}';
  58. }
  59. public static class Builder {
  60. private HttpUrl url;
  61. private String method;
  62. private Headers.Builder headers;
  63. private RequestBody body;
  64. private Object tag;
  65. public Builder() {
  66. this.method = "GET";
  67. this.headers = new Headers.Builder();
  68. }
  69. private Builder(Request request) {
  70. this.url = request.url;
  71. this.method = request.method;
  72. this.body = request.body;
  73. this.tag = request.tag;
  74. this.headers = request.headers.newBuilder();
  75. }
  76. public Builder url(HttpUrl url) {
  77. if (url == null) throw new NullPointerException("url == null");
  78. this.url = url;
  79. return this;
  80. }
  81. /**
  82. * Sets the URL target of this request.
  83. *
  84. * @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this
  85. * exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs.
  86. */
  87. public Builder url(String url) {
  88. if (url == null) throw new NullPointerException("url == null");
  89. // Silently replace websocket URLs with HTTP URLs.
  90. if (url.regionMatches(true, 0, "ws:", 0, 3)) {
  91. url = "http:" + url.substring(3);
  92. } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
  93. url = "https:" + url.substring(4);
  94. }
  95. HttpUrl parsed = HttpUrl.parse(url);
  96. if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
  97. return url(parsed);
  98. }
  99. /**
  100. * Sets the URL target of this request.
  101. *
  102. * @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code
  103. * https}.
  104. */
  105. public Builder url(URL url) {
  106. if (url == null) throw new NullPointerException("url == null");
  107. HttpUrl parsed = HttpUrl.get(url);
  108. if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
  109. return url(parsed);
  110. }
  111. /**
  112. * Sets the header named {@code name} to {@code value}. If this request already has any headers
  113. * with that name, they are all replaced.
  114. */
  115. public Builder header(String name, String value) {
  116. headers.set(name, value);
  117. return this;
  118. }
  119. /**
  120. * Adds a header with {@code name} and {@code value}. Prefer this method for multiply-valued
  121. * headers like "Cookie".
  122. *
  123. * <p>Note that for some headers including {@code Content-Length} and {@code Content-Encoding},
  124. * OkHttp may replace {@code value} with a header derived from the request body.
  125. */
  126. public Builder addHeader(String name, String value) {
  127. headers.add(name, value);
  128. return this;
  129. }
  130. public Builder removeHeader(String name) {
  131. headers.removeAll(name);
  132. return this;
  133. }
  134. /** Removes all headers on this builder and adds {@code headers}. */
  135. public Builder headers(Headers headers) {
  136. this.headers = headers.newBuilder();
  137. return this;
  138. }
  139. /**
  140. * Sets this request's {@code Cache-Control} header, replacing any cache control headers already
  141. * present. If {@code cacheControl} doesn't define any directives, this clears this request's
  142. * cache-control headers.
  143. */
  144. public Builder cacheControl(CacheControl cacheControl) {
  145. String value = cacheControl.toString();
  146. if (value.isEmpty()) return removeHeader("Cache-Control");
  147. return header("Cache-Control", value);
  148. }
  149. public Builder get() {
  150. return method("GET", null);
  151. }
  152. public Builder head() {
  153. return method("HEAD", null);
  154. }
  155. public Builder post(RequestBody body) {
  156. return method("POST", body);
  157. }
  158. public Builder delete(RequestBody body) {
  159. return method("DELETE", body);
  160. }
  161. public Builder delete() {
  162. return delete(RequestBody.create(null, new byte[0]));
  163. }
  164. public Builder put(RequestBody body) {
  165. return method("PUT", body);
  166. }
  167. public Builder patch(RequestBody body) {
  168. return method("PATCH", body);
  169. }
  170. public Builder method(String method, RequestBody body) {
  171. if (method == null) throw new NullPointerException("method == null");
  172. if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
  173. if (body != null && !HttpMethod.permitsRequestBody(method)) {
  174. throw new IllegalArgumentException("method " + method + " must not have a request body.");
  175. }
  176. if (body == null && HttpMethod.requiresRequestBody(method)) {
  177. throw new IllegalArgumentException("method " + method + " must have a request body.");
  178. }
  179. this.method = method;
  180. this.body = body;
  181. return this;
  182. }
  183. /**
  184. * Attaches {@code tag} to the request. It can be used later to cancel the request. If the tag
  185. * is unspecified or null, the request is canceled by using the request itself as the tag.
  186. */
  187. public Builder tag(Object tag) {
  188. this.tag = tag;
  189. return this;
  190. }
  191. public Request build() {
  192. if (url == null) throw new IllegalStateException("url == null");
  193. return new Request(this);
  194. }
  195. }
  196. }

上面的代码很长,其实我们关注的有几点如下:
Request构造器

  1. private Request(Builder builder) {
  2. this.url = builder.url;
  3. this.method = builder.method;
  4. this.headers = builder.headers.build();
  5. this.body = builder.body;
  6. this.tag = builder.tag != null ? builder.tag : this;
  7. }

注意是个私有构造器,而且参数是Builder实例,这个构造器限制了必须通过Builder实例才能生成Request实例,并且,只能在看得到构造器的地方调用如下所示:

  1. public Request build() {
  2. if (url == null) throw new IllegalStateException("url == null");
  3. return new Request(this);
  4. }

这个成员方法build()构造了Request实例,所以有必要看一下如何获取Build实例

  1. public Builder() {
  2. this.method = "GET";
  3. this.headers = new Headers.Builder();
  4. }
  5. private Builder(Request request) {
  6. this.url = request.url;
  7. this.method = request.method;
  8. this.body = request.body;
  9. this.tag = request.tag;
  10. this.headers = request.headers.newBuilder();
  11. }

提供了两个重载的构造器,而且分别是私有的和公有的,所以对于类的外部来说,只能通过公有构造器获取Build实例,然后在调用成员方法build()构造Request实例,当然如果如果想反向获取Build实例,也可以直接通过Request的成员方法获取,如下:

  1. public Builder newBuilder() {
  2. return new Builder(this);
  3. }

对于上面的设计,可能有些人会问,为啥非得通过Build这个类来间接的完成Request的构建呢?

原因个人理解是,因为Request的参数比较多,通过Build可以有效的管理这些参数,另外还可以做有效性检查等其他操作。

至于其他的原因读者可以另行补充。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注