構建器(Builder模式)的使用
靜態工廠和構造器有個共同的侷限性:他們都不能很好地擴充套件到大量的可選引數。例如用一個類表示包含食品外面顯示的營養成分標籤。這些標籤中有幾個屬性是必須的:每份的含量,以及每份的卡路里。還有幾個可選屬性:總脂肪量、飽和脂肪量、轉化脂肪等等。
1、重疊構造器模式
對於這樣的類,應該用哪種構造器或者靜態方法來編寫呢?我們先看看重疊構造器模式,在這種模式下,你提供第一個只有必要引數的構造器,第二個構造器有一個可選引數,第三個有兩個可選引數,以此類推,最後一個構造器包含所有可選引數。如下面事例
public class NutritionFacts {
// required
private int servingSize;
// required
private int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}
當你想要建立例項的時候,就利用引數列表最短的構造器,但該列表中包含了要設定的所有引數:NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27); 這個構造器呼叫通常需要許多你本不想設定的引數,但還是不得不為它們傳遞值,在這個例子中,我們給fat傳遞一個值為0。如果僅僅是這6個引數,看起來不算太糟糕,問題是隨著引數數目的增加,他很快就失去了控制。 重疊構造器模式是可行,但是有許多引數的時候,程式碼編寫就變得很複雜了,如果想知道引數的具體意思,就必須得很仔細的去閱讀,而且如果不小心弄錯了引數的位置,編譯器不會報錯,但是程式執行的時候就會出現錯誤行為。
2、JavaBean模式
遇到許多構造器引數的時候,還有第二種代替方法,即JavaBean模式。在這種模式下,呼叫一個無參構造器來建立物件,然後呼叫setter方法來設定每個必要的引數,以及每個相關的可選引數。
public class NutritionFacts {
// required
private int servingSize;
// required
private int servings;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}
public void setServings(int servings) {
this.servings = servings;
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setFat(int fat) {
this.fat = fat;
}
public void setSodium(int sodium) {
this.sodium = sodium;
}
public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
}
遺憾的是,JavaBean模式自身也有很嚴重的缺點,因為構造過程被分到了幾個呼叫中,在構造過程中JavaBean可能處於不一致的狀態。類無法僅僅通過檢驗構造器引數的有效性來保證一致性。試圖使用處於不一致狀態的物件,將會導致失敗。所以需要程式設計師付出額外的努力來確保它的執行緒安全。
3、Builder模式
幸運的是,還有第三種替代方法,既能保證重疊構造器模式那樣的安全性,也能保證JavaBean模式那麼好的可讀性,這就是Builder模式。
public class NutritionFacts {
// required
private int servingSize;
// required
private int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public static class Builder {
// required
private int servingSize;
// required
private int servings;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int calories) {
this.calories = calories;
return this;
}
public Builder fat(int fat) {
this.fat = fat;
return this;
}
public Builder sodium(int sodium) {
this.sodium = sodium;
return this;
}
public Builder carbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
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;
}
}
呼叫方法為:
NutritionFacts cocaCola = new Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
這樣編寫程式碼很容易,更重要的是,易於閱讀。