前言
上一篇記錄了用靜態工廠 VS 構造器,本片繼續記錄書中的提到的其它的方式。
闡述
在第二節開篇,作者提出自己的見解。
靜態工廠和構造器有個共同的局限性,它們都不能很好的拓展到大量的可選參數。
本想使用自己在練習中的例子,但書中給的實例實在是優秀ug10后處理構造器下載,所以下文大部分引用書中的案例。比如,用一個類表示包裝食品的營養成分標簽,其中有幾個域是必須的:每份的含量、每罐的含量以及每份的卡路里。還有幾個可選域:總脂肪量、飽和脂肪量、轉化脂肪、膽固醇、鈉等等。大多數產品在幾個可選域中都會有非零的值。
重疊構造器
對于選用哪種方式來創建,按照我們常規的方式一般就會采用 重疊構造器 模式,這種模式中,提供的第一個構造器只需包含必要的參數,第二個構造器有一個可選參數,第二個構造器有兩個可選參數,以此類推,最后一個構造器包含所有的可選參數。以下是一個只顯示四個可選域的實例:
public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // optional
private final int fat; // (g) optional
private final int sodium; // (mg) optional
private final int carbohydrate; // (g) optional
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;
}
}
一般情況下,實例化對象的時候會變成以下這個樣子:
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
}
其中的某個參數可能是我們不想設置的,但是不得不為它們傳遞值。這個例子中,我們給 fat 傳遞了一個值為 0。如果僅僅是 6 個參數,還在我們的可控范圍ug10后處理構造器下載,問題是隨著功能和參數的增加,很快就會失去控制。
簡而言之,重疊構造器模式可行,但是當有許多參數的時候,客戶端的代碼會很難編寫,并且難以閱讀。
如果我們想知道具體的值是什么意思,必須仔細地去讀參數的注釋。如果是一長串類型相同的參數必然會導致一些微妙的錯誤,如果不小心顛倒了其中兩個參數的順序,編譯器并不會報錯,但是最終的結果可能會出現錯誤的行為。
這種情況下,還有第二種替代方式。
模式
這種模式下,先調用一個無參構造器來創建對象,然后通過調用 set 方法來設置每個必要的參數和可選參數,例如:
public class NutritionFacts {
// Parameters initialized to default values (if any)
private int servingSize = -1;
private int servings = -1;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() {}
// set function
public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
}
這種模式彌補了重疊構造器的不足,創建實例很容易,這樣的代碼可讀性也非常高:
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
}
美中不足的是, 模式自身有一些缺點:
類在構造過程中被分到了幾個調用中,在構造過程中 可能處于不一致的狀態
類無法僅僅通過檢驗構造器參數的有效性來保證一致。
模式使得把類做成不可變的可能性不復存在(后邊單獨更新),需要付出額外的努力確保它的線程安全
針對這種情況,書中給出了第三種替代方式。
建造者模式 它既能保證像重疊構造器模式那樣的安全性,也能保證像 模式那么好的可讀性
示例:
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 {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - 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 carbohydrate(int val) {
carbohydrate = 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;
}
}
注意 是不可變的,所有的默認參數都單獨放在一個地方, 的設值方法返回 本身,以便把調用鏈接起來,得到一個**流式的 API。**下面是調用示例:
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
}
這樣的客戶端代碼很容易編寫,更為重要的是易于閱讀, 模式模擬了明確命名的可選參數。
總結
非常喜歡的書中摘錄:
雖然本書中的規則不會百分之百地適用于任何時刻和任何場合,但是,它們確實體現了絕大多數情況下的最佳編程實踐。你不應該盲目的遵循這些規則,但偶爾有了充分的理由之后,可以去打破這些規則。同大多數學科一樣,學習編程藝術首先要學會基本的規則,然后才能知道什么時候去打破它。