使用 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 的建構函式中。