設計模式: 建造者模式

目錄

一、現實編碼中學習建造者模式

二、建造者模式的定義

三、簡單例子,通俗易懂

3.1 普通的對象構建

3.1 建造者模式構建對象

四 總結

怎樣使用Builder模式


 

一、現實編碼中學習建造者模式

在使用okhttp3的過程中,我們一般的使用過程如下:

public void testOkHttp() {
        RequestBody requestBody = RequestBody.create(MediaType.parse("Content-Type, application/json"), "");
        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder().url("https://test.api.com").post(requestBody).build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d(TAG, "onFailure: " + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.d(TAG, response.protocol() + " " + response.code() + " " + response.message());
                Headers headers = response.headers();
                for (int i = 0; i < headers.size(); i++) {
                    Log.d(TAG, headers.name(i) + ":" + headers.value(i));
                }
                Log.d(TAG, "onResponse: " + response.body().string());
            }
        });
    }

上面代碼中我們可以看到構建Request的代碼:

Request request = new Request.Builder().url("https://test.api.com").post(requestBody).build();

這裏就使用到了建造者模式。

 

二、建造者模式的定義

網絡上隨便找個定義如下:將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示

看起來十分的抽象,並不容易理解。

下面通過例子來理解,先將Request的源碼修改一下,修改成普通的對象,如下:


import android.support.annotation.Nullable;

import java.util.List;

import okhttp3.CacheControl;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.RequestBody;

public final class Request {
  private HttpUrl url;
  private String method;
  private Headers headers;
  private RequestBody body;
  private Object tag;
  
  public Request(){
  }
  
  public Request(HttpUrl url,String method,Headers headers,RequestBody body, Object tag) {
    this.url = url;
    this.method = method;
    this.headers = headers;
    this.body = body;
    this.tag = tag;
  }

  public void setUrl(HttpUrl url) {
    this.url = url;
  }

  public HttpUrl url() {
    return url;
  }

  public void setMethod(String method) {
    this.method = method;
  }

  public String method() {
    return method;
  }

  public void setHeaders(Headers headers) {
    this.headers = headers;
  }

  public Headers headers() {
    return headers;
  }

  public String header(String name) {
    return headers.get(name);
  }

  public List<String> headers(String name) {
    return headers.values(name);
  }

  public void setBody(RequestBody body) {
    this.body = body;
  }

  public @Nullable RequestBody body() {
    return body;
  }

  public void setTag(Object tag) {
    this.tag = tag;
  }

  public Object tag() {
    return tag;
  }


  public boolean isHttps() {
    return url.isHttps();
  }

  @Override public String toString() {
    return "Request{method="
        + method
        + ", url="
        + url
        + ", tag="
        + (tag != this ? tag : null)
        + '}';
  }

}

如果Request的代碼如上,那麼我們構建一個Request對象的步驟是直接通過構造函數構造一個Request對象,傳入要求的參數,如果使用的默認構造函數,則還需要調用不同的屬性的set方法設置初始值,說不定還會忘記,這裏這是少量的參數,當參數類型多樣,數量大的時候,構造對象實例那就更加的不方便和不易讀了;

下面是Request的未改動源碼:

/*
 * Copyright (C) 2013 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3;

import java.net.URL;
import java.util.List;
import javax.annotation.Nullable;
import okhttp3.internal.Util;
import okhttp3.internal.http.HttpMethod;

/**
 * An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself
 * immutable.
 */
public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }

  public HttpUrl url() {
    return url;
  }

  public String method() {
    return method;
  }

  public Headers headers() {
    return headers;
  }

  public String header(String name) {
    return headers.get(name);
  }

  public List<String> headers(String name) {
    return headers.values(name);
  }

  public @Nullable RequestBody body() {
    return body;
  }

  public Object tag() {
    return tag;
  }

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

  /**
   * Returns the cache control directives for this response. This is never null, even if this
   * response contains no {@code Cache-Control} header.
   */
  public CacheControl cacheControl() {
    CacheControl result = cacheControl;
    return result != null ? result : (cacheControl = CacheControl.parse(headers));
  }

  public boolean isHttps() {
    return url.isHttps();
  }

  @Override public String toString() {
    return "Request{method="
        + method
        + ", url="
        + url
        + ", tag="
        + (tag != this ? tag : null)
        + '}';
  }

  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;

    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.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }

    public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      return this;
    }

    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this
     * exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs.
     */
    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);
      }

      HttpUrl parsed = HttpUrl.parse(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code
     * https}.
     */
    public Builder url(URL url) {
      if (url == null) throw new NullPointerException("url == null");
      HttpUrl parsed = HttpUrl.get(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

    /**
     * Sets the header named {@code name} to {@code value}. If this request already has any headers
     * with that name, they are all replaced.
     */
    public Builder header(String name, String value) {
      headers.set(name, value);
      return this;
    }

    /**
     * Adds a header with {@code name} and {@code value}. Prefer this method for multiply-valued
     * headers like "Cookie".
     *
     * <p>Note that for some headers including {@code Content-Length} and {@code Content-Encoding},
     * OkHttp may replace {@code value} with a header derived from the request body.
     */
    public Builder addHeader(String name, String value) {
      headers.add(name, value);
      return this;
    }

    public Builder removeHeader(String name) {
      headers.removeAll(name);
      return this;
    }

    /** Removes all headers on this builder and adds {@code headers}. */
    public Builder headers(Headers headers) {
      this.headers = headers.newBuilder();
      return this;
    }

    /**
     * Sets this request's {@code Cache-Control} header, replacing any cache control headers already
     * present. If {@code cacheControl} doesn't define any directives, this clears this request's
     * cache-control headers.
     */
    public Builder cacheControl(CacheControl cacheControl) {
      String value = cacheControl.toString();
      if (value.isEmpty()) return removeHeader("Cache-Control");
      return header("Cache-Control", value);
    }

    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 delete(@Nullable RequestBody body) {
      return method("DELETE", body);
    }

    public Builder delete() {
      return delete(Util.EMPTY_REQUEST);
    }

    public Builder put(RequestBody body) {
      return method("PUT", body);
    }

    public Builder patch(RequestBody body) {
      return method("PATCH", body);
    }

    public Builder method(String method, @Nullable RequestBody body) {
      if (method == null) throw new NullPointerException("method == null");
      if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
      if (body != null && !HttpMethod.permitsRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must not have a request body.");
      }
      if (body == null && HttpMethod.requiresRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must have a request body.");
      }
      this.method = method;
      this.body = body;
      return this;
    }

    /**
     * Attaches {@code tag} to the request. It can be used later to cancel the request. If the tag
     * is unspecified or null, the request is canceled by using the request itself as the tag.
     */
    public Builder tag(Object tag) {
      this.tag = tag;
      return this;
    }

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

相對應的構建Request對象的方式爲:

Request request = new Request.Builder()
        .url("https://test.api.com")
        .post(requestBody)
        .build();
採用鏈式調用,看起來構建過程十分清晰,一目瞭然。

其實在android中有很多地方使用了建造者模式,比如AlertDialog的創建

AlertDialog.Builder builder=new AlertDialog.Builder(this);
AlertDialog dialog=builder.setTitle("title")
		.setIcon(android.R.drawable.ic_dialog_alert)
		.setView(R.layout.myview)
		.setPositiveButton(R.string.positive, new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {

			}
		})
		.setNegativeButton(R.string.negative, new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {

			}
		})
		.create();
dialog.show();

 

三、簡單例子,通俗易懂

這裏借鑑一篇寫的簡單易懂的文章:https://www.cnblogs.com/android-blogs/p/5530239.html

3.1 普通的對象構建

定義一個Person類代表人類的抽象,然後我們根據它來創建不同的人類,有不同的name,age,height和weight

public class Person {
    private String name;
    private int age;
    private double height;
    private double weight;

    public Person() {
    }

    public Person(String name) {
	    this.name = name;
    }

    public Person(String name, int age) {
	    this.name = name;
	    this.age = age;
    }

    public Person(String name, int age, double height) {
	    this.name = name;
	    this.age = age;
	    this.height = height;
    }

    public Person(String name, int age, double height, double weight) {
	    this.name = name;
	    this.age = age;
	    this.height = height;
	    this.weight = weight;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }
}

然後我們使用不同的構造函數創建實例人類:

Person personA=new Person();
Person personB=new Person("張三");
Person personC=new Person("李四",18);
Person personD=new Person("王五",21,170);
Person personE=new Person("趙六",17,181,173);

看上面的對象創建過程似乎也很簡單,但是面對構造函數傳入的數值的時候,並不能很容易的知道這些數字是什麼意思。181是身高還是體重,173是體重還是身高。

 

3.1 建造者模式構建對象

將上面例子修改一下如下:

public class Person {
    private String name;
    private int age;
    private double height;
    private double weight;

    privatePerson(Builder builder) {
        this.name=builder.name;
        this.age=builder.age;
        this.height=builder.height;
        this.weight=builder.weight;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    static class Builder{
        private String name;
        private int age;
        private double height;
        private double weight;
        public Builder name(String name){
            this.name=name;
            return this;
        }
        public Builder age(int age){
            this.age=age;
            return this;
        }
        public Builder height(double height){
            this.height=height;
            return this;
        }

        public Builder weight(double weight){
            this.weight=weight;
            return this;
        }

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

使用如下,一下就清晰了,也能清楚的知道賦值的是什麼屬性

Person.Builder builder=new Person.Builder();
Person person=builder
		.name("趙六")
		.age(17)
		.height(181)
		.weight(173)
		.build();

 

四 總結

怎樣使用Builder模式

這裏總結一下通用步驟:

1)在普通的類中定義一個靜態內部類Builder,Builder內部的成員變量和外部類成員變量一一對應,也就是一樣的;

2)Builder類中定義一系列的方法,用於給Builder中的成員變量賦值,並返回當前對象本身,也就是返回this關鍵字,這裏方便鏈式調用;

3)Builder類提供一個build方法用於創建外部類的實例對象,該方法內部調用了外部類的一個私有構造函數,該構造函數的參數就是內部類Builder;

4)外部類提供一個私有構造函數供內部類調用,在該構造函數中完成成員變量的賦值,取值爲Builder對象中對應的值。

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