1. 程式人生 > >構建器(Builder模式)的使用

構建器(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();

這樣編寫程式碼很容易,更重要的是,易於閱讀。