1. 程式人生 > >使用 Builder 模式構造 JavaBean 的好處

使用 Builder 模式構造 JavaBean 的好處

我們一般在構造一個javaBean 物件時,無非有三種寫法:

1.直接通過建構函式傳參的方式設定屬性,這種方法如果屬性過多的話會讓建構函式十分臃腫,而且不能靈活的選擇只設置某些引數。

2.採用重疊構造區模式,先寫第一個只有必要引數的構造器,第二個構造器有一個可選引數,第三個構造器有兩個可選引數,以此類推;如果引數比較多時,類裡面會出現一堆構造方法,並且閱讀困難,很容易就把兩個屬性引數寫顛倒位置了,編譯不會出錯,但執行就會出錯了

3.採用Javabean 的寫法,寫一堆屬性的setter方法,通過生成物件,讓後呼叫setter方法給屬性賦值。  這種方法有個劣勢就是構造的過程被分到幾個呼叫中,在構造中可能處於不一致狀態,無法保證一致性。

4.採用Builder 模式構造物件,當一個類的引數多的情況下,使用重疊構造器模式客戶端程式碼會很難編寫,並且可讀性差;使用javabean模式,呼叫一個無參的構造器,然後呼叫setter方法來設定每個必要的引數。但是javabean自身有著嚴重的缺點,因為構造過程被分到幾個呼叫中,在構造javabean可能處於不一致的狀態,類無法僅僅通過檢驗構造器引數的有效性來保證一致性。另一點不足之處,javabean模式阻止了把類做成不可變的可能,這就需要程式設計師付出額外的努力來確保他的執行緒安全; build模式 既能保證像重疊構造器那樣的安全,也能實現JavaBean模式那樣的可讀性。 


使用build模式的步驟: 
(1)不直接生成想要的物件,而是讓客戶端利用所有必要的引數呼叫構造器(或者靜態工廠),得到一個build物件。 
(2)然後讓客戶端在build物件上呼叫類似的setter方法來設定每個相關的可選引數, 
(3)最後,客戶端呼叫無參的build方法來生成不可變的物件。這個builder是它構建的靜態成員類。

下面就說一下 Builder 模的使用,這個例子是okhtttp 中的request 物件的構造:

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final RequestBody body;
  final Object tag;

  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 RequestBody body() {
    return body;
  }

  public Object tag() {
    return tag;
  }

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

  
  public CacheControl cacheControl() {
    CacheControl result = cacheControl;
    return result != null ? result : (cacheControl = CacheControl.parse(headers));
  }

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


  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;
    }


    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);
    }

    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);
    }

    public Builder header(String name, String value) {
      headers.set(name, value);
      return this;
    }

    public Builder addHeader(String name, String value) {
      headers.add(name, value);
      return this;
    }

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

    public Builder headers(Headers headers) {
      this.headers = headers.newBuilder();
      return this;
    }

    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(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, 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;
    }

    public Builder tag(Object tag) {
      this.tag = tag;
      return this;
    }


    /* 
     * Request 物件建立器,想得到一個Request 物件必須使用build 方法, 
     * 在方法中增加對Builder引數的驗證,並以異常的形式告訴給開發人員。 
     */  		
    public Request build() {
    /**
     * 比如下面判斷如果 url 是null的話就會丟擲異常
     */
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
  }
}

可以看出 Builder 是Request  靜態的內部類,並且 Builder 中的屬性是和 Request 中的一致的,所以得屬性設定都在 Builder 中,Request 中只有獲取屬性的方法

使用 Builder 構造一個 Request 物件的寫法:

Request request = new Request.Builder()
                .url("http://www.weather.com.cn/data/sk/101010100.html")
                .addHeader("header","header")
                .put("RequestBody")
                .build();

採用javaBean 寫法的缺點就是, 一但呼叫 new Request() 建構函式後,物件就被建立了,以後在呼叫 set 方法設定屬性的時候這裡設定一下,其他地方又設定一下,無法保證物件的狀態一致性,而且程式碼的可讀性很差

1. Builder 方式建立的物件,在呼叫 build() 方法之前是不會建立Request 物件的,所有的屬性設定都必須在 build() 方法之前,而且建立了 Request 物件後就不可以更改其屬性了,這就保證了物件狀態的唯一性,而且程式碼的可讀性也提高了。

2.如果有些引數是必填的,可以加到 Builder 的建構函式中。