創建對象的三種模式
- 靜態工廠和構造器模式:將所有參數傳遞到構造函數中,這種方式不能很好地擴展到大量的可選參數。
- JavaBean模式:調用一個無參數構造器,然后調用setter方法來設置每個必要的參數以及可選參數。
- Builder模式:讓客戶端利用所有必要的參數調用構造器(或靜態工廠方法),得到一個builder對象,然后客戶端在builder對象上調用類似setter方法,來設置每個相關的可選參數。
構造器模式:
提供一個只有必要參數的構造器,第二個構造器有一個可選參數,第三個構造器有兩個可選參數.....
// Telescoping constructor pattern - does not scale well!
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;
}
}
這種情況下,當我們調用某個構造器時,有些你本不想設置的參數,但還是得為它們傳值。
NutritionFacts cocaCola =new NutritionFacts(240, 8, 100, 0, 35, 27);
如上,我們如果不需要fat參數,但卻不得不傳遞一個值給它,以滿足構造器需求。此外,當構造器存在多個參數時,客戶端代碼會變得很難編寫,并且難以閱讀。
JavaBean模式:
調用一個無參數構造器,然后調用setter方法來設置每個必要的參數以及可選參數。
// JavaBeans Pattern - allows inconsistency, mandates mutability
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;
}
}
調用方法:
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
使用JavaBean模式,可以解決構造器模式可選參數問題,并且代碼的可讀性也提高了不少。
但JavaBean本身也存在嚴重的缺點:
- 因為構造過程中被劃分到了幾個調用中,在構造過程中JavaBean可能處于不一致的狀態。類無法僅僅通過檢驗構造器參數的有效性來保證一致性。
- JavaBean模式下無法將類設置為不可變(即final類型),那么這樣需要我們去確保它的線程安全。
Builder模式:
讓客戶端利用所有必要的參數調用構造器(或者靜態工廠),得到一個builder對象。然后客戶端在builder對象上調用類似于setter的方法,來設置每個相關的可選參數。最后,客戶端調用無參的build方法來生成不可變的對象。
在NutritionFacts類中構造一個嵌套類Builder,來構建builder對象:
public static class Builder{
//其中servingSize和servings為必要參數,因此設置為final類型,通過構造器傳入的參數來初始化
private final int servingSize;
private final 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;
}
//設置可選參數,并返回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方法獲取到NutritionFacts對象
public NutritionFacts build(){
return new NutritionFacts(this);
}
}
在NutritionFacts中通過builder對象來初始化參數:
public class NutritionFacts {
//都設置為final類型,不可變對象,以保證線程的安全
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
//傳入builder對象,通過builder對象來初始化參數
public NutritionFacts(Builder builder){
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
完整代碼:
public class NutritionFacts {
//都設置為final類型,不可變對象,以保證線程的安全
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
//傳入builder對象,通過builder對象來初始化參數
public NutritionFacts(Builder builder){
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static class Builder{
//其中servingSize和servings為必要參數,因此設置為final類型,通過構造器傳入的參數來初始化
private final int servingSize;
private final 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;
}
//設置可選參數,并返回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方法獲取到NutritionFacts對象
public NutritionFacts build(){
return new NutritionFacts(this);
}
}
}
調用方法:
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
- 可見Builder模式不僅能保證構造器模式下的安全性(設置參數為final類型),也能保證像JavaBean模式下良好的可讀性。
- 但其實Builder模式也存在一些不足之處,為了創建對象,必須先創建它的構建器(即Builder對象)。雖然構建器的開銷在實踐中可能不那么明顯,但是在某些十分注重性能的情況下,這可能就是個問題了。