选择语言
durumis AI 总结的文章
- 介紹了各種用於有效管理具有許多參數的建構函式的模式,並比較分析了分層建構函式模式、Java Beans 模式和建構器模式的優缺點。
- 建構器模式具有提高程式碼可讀性和穩定性的優點,特別是與分層設計的類別一起使用時,效果更佳。
- 建構器模式可以使程式碼更清晰,但它也具有建構器物件建立和程式碼冗長等缺點。
遞增式建構器模式
靜態工廠和建構函數在面對大量參數時都難以應對。例如,如果一個類別有 6 個欄位,而我們想在 2 個參數、3 個參數 ... 等情況下分別使用建構函數,可以使用遞增式建構器模式,如下所示。
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 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 NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0, 0, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
但即使這樣,當參數太多時,在閱讀程式碼時,我們可能會混淆每個值的含義,並且可能會將類型相同的參數混淆並輸入錯誤的值。
JavaBeans 模式
JavaBeans 模式使用無參數的建構函數建立物件,然後呼叫 setter 方法設定所需的參數值。
public class NutritionFactsWithJavaBeansPattern {
private int servingSize = -1; // 必需。沒有預設值。
private int servings = -1; // 必需。沒有預設值。
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
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;
}
JavaBeans 模式即使在參數數量增加時,也能避免混淆地建立實例。但是,要建立一個物件,需要呼叫多個 setter 方法,並且在物件完全完成之前,一致性會被破壞。因此,類別不能是不可變的。
建構器模式
建構器模式結合了遞增式建構器模式的穩定性和 JavaBeans 模式的可讀性,是常用的模式。
客戶端不是直接建立所需的物件,而是呼叫帶有必需參數的建構函數來獲取建構器物件。然後,建構器物件提供一種 setter 方法,用於設定所需的選項參數。最後,呼叫不帶參數的 build() 方法來獲取所需的物件。
public class NutritionFactsWithBuilderPattern {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
private NutritionFactsWithBuilderPattern(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static class Builder {
private final int servingSize;
private final int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
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 Builder carbohydrate(int val) {
calories = val;
return this;
}
public NutritionFactsWithBuilderPattern build() {
return new NutritionFactsWithBuilderPattern(this);
}
}
Builder 類別中的建構函數接收必需參數,其餘的選項參數通過一種 setter 方法填寫。最後,通過 build() 方法建立完整的 NutritionFactsWithBuilderPattern 物件。 NutritionFactsWithBuilderPattern 類別是不可變的,因為建構器的 setter 方法返回建構器本身,因此可以連續呼叫。這種方式被稱為流暢 API 或方法鏈。
NutritionFactsWithBuilderPattern nutritionFacts =
new NutritionFactsWithBuilderPattern.Builder(240, 8)
.calories(100)
.sodium(35)
從客戶端角度來看,建構器模式使程式碼更易於編寫和閱讀。
建構器模式與分層設計類別的良好搭配
public abstract class Pizza {
public enum Topping {
HAM, MUSHROOM, ONION, PEPPER, SAUSAGE
}
final Set toppings;
Pizza(Builder> builder) {
toppings = builder.toppings.clone();
}
abstract static class Builder> {
private EnumSet toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(topping);
return self();
}
abstract Pizza build();
protected abstract T self();
}
Pizza.Builder 類別是一個使用遞迴類型約束的泛型類型,它通過添加抽象方法 self() 使子類別能夠在不進行類型轉換的情況下支援方法鏈。在子類別中,只需將此抽象方法的返回值設定為自身即可。
現在,讓我們看一下 Pizza 的子類別紐約披薩和卡爾佐尼披薩,體驗一下建構器模式的靈活性。
public class NyPizza extends Pizza {
public enum Size {
SMALL, MEDIUM, LARGE
}
private final Size size; // 必需參數
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
public static class Builder extends Pizza.Builder {
private final Size size;
public Builder(Size size) {
this.size = size;
}
@Override
NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
}
public class CalzonePizza extends Pizza {
private final boolean sauceInside; // 選擇參數
private CalzonePizza(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
public static class Builder extends Pizza.Builder {
private boolean sauceInside = false;
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override
CalzonePizza build() {
return new CalzonePizza(this);
}
@Override
protected Builder self() {
return this;
}
}
每個子類別的建構器定義的 build() 類別都返回具體的子類別。子類別方法返回的類型不是父類別方法返回的類型,而是其子類型,這稱為協變返回類型化。使用此功能,客戶端不需要進行類型轉換。
NyPizza nyPizza = new NyPizza.Builder(Size.SMALL)
.addTopping(Topping.SAUSAGE)
.addTopping(Topping.ONION)
.build();
CalzonePizza calzonePizza = new CalzonePizza.Builder()
.addTopping(Topping.HAM)
.sauceInside()
從客戶端角度來看,可以混合使用 Pizza 的枚舉和每個子類別的枚舉,並使用各自的適當方法完成物件的建立。
建構器模式的缺點
- 需要建立建構器物件。
- 程式碼冗長。
總結
如果建構函數或靜態工廠方法需要處理大量參數,請考慮使用建構器模式。
來源
- Effective Java