builder模式,目的在於:抽離複雜對象的構造函數,讓我們可以通過多種方法的排列組合構建複雜的對象。如果構造器參數過多,可以考慮 builder 模式 ...
Builder 模式的目的?
抽離複雜對象的構造函數,讓我們可以通過多種方法的排列組合構建複雜的對象。如果構造器參數過多,可以考慮 builder 模式
這樣說也有點抽象,舉個例子吧。
舉個例子
比如 非常熱門的消息隊列RabbitMQ 的 AMQP.BasicProperties
因為它的屬性比較多,所以構造函數也是挺嚇人的。
我看到也不太想調用。
如果現在要構造一條消息
- 投遞模式(delivery mode)為 2
- 優先順序(priority)是 2
- content-type 為 text/plain
在沒有 builder 模式之前,是這樣構造的
new AMQP.BasicProperties("text/plain",null,null,2,1,null,null,null,null,null,null,null,null,null);
痛苦啊!!!不信,你自己也可以嘗試構造一下。
- 構造函數有很多你不想設置的參數
- 你要看準,哪個參數要賦值,哪個參數不賦值,一不小心就可能出錯了。而這裡有 14 個參數。。。
- 維護性差,寫完代碼再看一下,也看不出這個參數究竟是什麼意思。還要點進去,一個一個參數地看才知道是什麼意思
而用了 Builder 模式後。
new AMQP.BasicProperties.Builder()
.contentType("text/plain")
.deliveryMode(2)
.priority(1)
.build();
舒暢!!!
Builder 是如何實現?
很簡單。
- 在
BasicProperties
中添加一個叫Builder
的內部類 - Builder 中所有欄位和
BasicProperties
類是完全一致的 - 而
Builder
實例在調用build
函數的時候,再調用BasicProperties
的構造函數構造對象。
代碼如下
public static class BasicProperties{
private String contentType;
private String contentEncoding;
private Map<String,Object> headers;
private Integer deliveryMode;
private Integer priority;
//... 還有很多屬性
public BasicProperties(
String contentType,
String contentEncoding,
Map<String,Object> headers,
Integer deliveryMode,
//...
String clusterId){
this.contentType = contentTypel;
this.contentEncoding = contentEncoding;
//...
}
public static final class Builder {
private String contentEncoding;
private Map<String,Object> headers;
private Integer deliveryMode;
private Integer priority;
//.. 和BasicProperties的欄位一致的。
public Builder contentType(String contentType){
this.contentType = contentType;
return this;
}
public Builder contentEncoding(String contentEncoding){
this.contentEncoding = contentEncoding;
return this;
}
public BasicProperties build() {
return new BasicProperties
( contentType
contentEncoding,
//還有很多屬性
);
}
}
}
分析
Builder 模式的好處
- 不用花太多心思去記構造器的順序,在 ide 中輸入一個點就有自動提示了
- 好維護,很容易看到看明白這是什麼屬性
壞處
- 構造對象就要先調用 Buidler 構造器,多了構造器的開銷
- 類的關係變得複雜了
其他的做法
如果不用 Builder 模式,有其他的做法嗎?
重疊構造器?
比如,上面的例子,我構造的消息只需 投遞模式(delivery mode)、優先順序(priority)、 content-type ,專門為這幾個參數弄個專門的構造函數,可以嗎?
調用就變成這樣了。
new AMQP.BasicProperties("text/plain",2,1)
可以,
- 但依然不太好看。
- 如果有不同的需求,各種屬性都排列組合一下也麻煩。
- 不實際,因為類欄位的類型可能會是一樣的,有些組合註定不行
javaBean 模式呢?
BasicProperties p = new AMQP.BasicProperties();
p.setContentType("text/plain");
p.setDeliveryMode(2);
p.setPriority(1);
在《effective java》中就探討過這個可能,書中是這樣說的
因為構造過程被分到幾個調用中,在構造過程中 javaBean 可能處於不一致的狀態。類無法僅僅通過檢驗構造器參數的有效性來保證一致性。試圖使用處於不一致狀態的對象,將導致失敗,這種失敗與包含錯誤的代碼大相徑庭,因此它調試起來十分困難。與此相關的另一點不足在於,javaBeans 模式阻止了把類做成不可變的可能,這需要程式員付出格外的努力來確保它的線程安全。
這話就有點摸不著頭腦,什麼意思。讀了幾次也有點懵。
其實意思是大概的,
- javaBean 是構造與欄位賦值分離的,有可能 線程 1 在給對象 Obj 賦值,還沒有賦完成的時候,線程 2 就拿了 Obj 的值了,就不一致了
- 如果 Obj 的欄位全都是 final 的,不會出現上面那種情況,但欄位只能會通過構造函數賦值(builder 模式也行),不能使用 javaBeans 的 setXXX 函數賦值了。
- 所以有多線程要求的,比如是傳給消息隊列的對象,程式員要保證下線程安全。
- 這是一個開放開閉的問題,Javabean 這樣的寫法確實和完全開放沒啥區別,如果欄位確定下來不用改了就最好設為 final 。
以上