問(wèn)題提出
假如一個(gè)類(lèi)有很多域,域中有一些必需參數(shù)和大量可選參數(shù),那么靜態(tài)工廠和構(gòu)造器需要提供一個(gè)只含必要參數(shù)的構(gòu)造器,然后第二個(gè)構(gòu)造器需要有一個(gè)可選參數(shù),第三個(gè)有兩個(gè)可選參數(shù),依次類(lèi)推,最后一個(gè)構(gòu)造器包含所有可選參數(shù)。
這樣重疊構(gòu)造器模式可行,但是當(dāng)有許多參數(shù)的時(shí)候,客戶(hù)端代碼會(huì)很難編寫(xiě),并且較難閱讀。
解決方案
- 使用JavaBeans模式
即調(diào)用一個(gè)無(wú)參構(gòu)造器來(lái)創(chuàng)建對(duì)象,然后調(diào)用setter方法來(lái)設(shè)置每個(gè)必要的參數(shù)和需要的可選參數(shù)。
這樣創(chuàng)建實(shí)例很容易,并且代碼也容易理解閱讀。But,構(gòu)造過(guò)程被分到了幾個(gè)調(diào)用中,而不是一下子就完成,這樣在構(gòu)造過(guò)程中JavaBean可能處于不一致?tīng)顟B(tài),可能會(huì)導(dǎo)致錯(cuò)誤!有時(shí)候可以通過(guò)手工“凍結(jié)”對(duì)象來(lái)解決這一問(wèn)題,但是實(shí)踐中比較少用。另外JavaBean使得類(lèi)不能做成不可變的,這樣程序員要付出額外努力確保線(xiàn)程安全。 - 使用Builder模式
這種方法不直接生成想要的對(duì)象,而是讓客戶(hù)端利用所有必要的參數(shù)調(diào)用構(gòu)造器(或靜態(tài)工廠),得到一個(gè)builder對(duì)象,然后在builder對(duì)象上調(diào)用類(lèi)似于setter的方法來(lái)設(shè)置每個(gè)相關(guān)的可選參數(shù),最后利用無(wú)參的build方法來(lái)生成不可變的對(duì)象。這個(gè)builder對(duì)象的類(lèi)是它構(gòu)建的類(lèi)的靜態(tài)成員類(lèi)。如下所示:
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
//compulsory paras
private final int servingSize;
private final int servings;
//optional paras - initialized to default values
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public NutritionFacts build(){
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder){
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
builder的setter返回builder本身,這樣調(diào)用可以鏈接起來(lái)。
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
.calories(100)
.sodium(35).build();
利用build方法進(jìn)行約束條件檢驗(yàn),將參數(shù)從builder拷貝到對(duì)象中之后,在對(duì)象域而不是builder域?qū)λ麄冞M(jìn)行檢驗(yàn)。若違反約束條件,build方法應(yīng)該拋出IllegalStateException,并告知違反哪個(gè)約束條件。
另一種檢驗(yàn)情況實(shí)在builder中的setter方法中檢驗(yàn)。
Builder模式也有不足,比如創(chuàng)建構(gòu)造器會(huì)造成額外的開(kāi)銷(xiāo),并且Builder模式還比重疊構(gòu)造器模式更加冗長(zhǎng)。
總結(jié)
如果類(lèi)的構(gòu)造器或靜態(tài)工廠中具有多個(gè)參數(shù),特別是大多數(shù)參數(shù)都是可選的時(shí)候,Builder模式是種不錯(cuò)的選擇。這樣客戶(hù)端代碼將更容易閱讀和編寫(xiě),構(gòu)造器也比JavaBeans更加安全。