Builder Pattern
阿新 • • 發佈:2017-07-02
cto exce 嚴重 屬性設置 不一致 methods 缺點 分表 對象
JavaBeans模式
① Builder方式創建的對象,在調用 build() 方法之前是不會創建NutritionFacts 對象的,所有的屬性設置都必須在 build() 方法之前,而且創建了NutritionFacts 對象後就不可以更改其屬性了,這就保證了對象狀態的唯一性,而且代碼的可讀性也提高了。② 如果有些參數是必填的,可以加到 Builder 的構造函數中缺點:Builder模式的確也有自身不足,為了創建對象,必須先創建它的構建器,在某些十分註重性能的情況下,可能就成為問題了。
遇到多個構造器形參時要考慮用構建器
引言
遇到多個構造器時要考慮用構建器(builder)重疊構造器(telescoping constructor)缺點:① 當有許多參數的時候,客戶端代碼會很難編寫,並且仍然較難以閱讀。② 如果讀者想知道那些值是什麽意思,必須很仔細地數著這些參數來探個究竟。如果客戶端不小心顛倒了其中兩個參數的順序,編譯器也不會出錯,但是在程序運行時會出現錯誤行為。
// Telescoping constructor pattern - does not scale well! - Pages 11-12
/**
* 營養成分表
*/
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);
}
}
JavaBeans模式
缺點:① 無法保證一致性:JavaBean模式自身有著很嚴重的缺點,因為構造過程被分到了幾個調用中,在構造過程中JavaBean可能處在不一致的狀態。試圖使用處於不一致狀態的對象,將會導致失敗,這種失敗調試起來十分困難② 阻止了把類做成不可變
// JavaBeans Pattern - allows inconsistency, mandates mutability - Pages 12-13
public class NutritionFacts {
// Parameters initialized to default values (if any)
private int servingSize = -1; // Required; no default value
private int servings = -1; // " " " "
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() { }
// Setters
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) {
// 調用一個無參構造器來創建對象,然後調用setter()方法來設置
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
}
}
Builder模式
優點:
// Builder Pattern - Pages 14-15
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;
/**
* 註意是static修飾, 作為NutritionFacts的靜態成員類
* 詳細的討論請參考:
* https://stackoverflow.com/questions/5007355/builder-pattern-in-effective-java
*/
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) {
System.out.println("NutritionFacts.Builder init this[" + this + "]");
this.servingSize = servingSize;
this.servings = servings;
}
// builder的setter()方法返回builder本身,以便可以把調用鏈接起來
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;
}
/**
* build()方法可以檢驗這些約束條件
* 如果違反了任何約束條件build方法就應該拋出IllegalSelectorException
*/
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
/**
* 註意這是private修飾的
*/
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static void main(String[] args) {
// 不直接生成想要的對象,而是讓客戶端利用所有必要的參數調用NutritionFacts.Builder(int, int)創建一個builder對象
// 然後客戶端在builder對象調用類似於setter的方法,來設置每個相關的可選參數
// 最後客戶端調用無參的build()方法來生成不可變的對象NutritionFacts
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
NutritionFacts cocaCola2 = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).fat(7).build();
}
}
① Builder方式創建的對象,在調用 build() 方法之前是不會創建NutritionFacts 對象的,所有的屬性設置都必須在 build() 方法之前,而且創建了NutritionFacts 對象後就不可以更改其屬性了,這就保證了對象狀態的唯一性,而且代碼的可讀性也提高了。② 如果有些參數是必填的,可以加到 Builder 的構造函數中缺點:Builder模式的確也有自身不足,為了創建對象,必須先創建它的構建器,在某些十分註重性能的情況下,可能就成為問題了。
參考
- Builder Pattern in Effective Java
- Too Many Parameters in Java Methods, Part 3: Builder Pattern
Builder Pattern