天天看點

《Effective Java》2、多個構造器參數時考慮builder1、建立對象的方式

1、建立對象的方式

靜态工廠和構造器模式:

不能很好地擴充到大量的可選參數。比如用一個類表示包裝食品外面顯示的營養成分标簽。這些标簽中有幾個域是必需的:每份的含量、每罐的含量以及每份的卡路裡。還有超過2 0 個的可選域: 總脂肪量、飽和脂肪量、轉化脂肪、膽固醇、納,等等。大多數産品在某幾個可選域中都會有非零的值。

對于這樣的類,應該用哪種構造器或者靜态工廠來編寫呢?程式員一向習慣采用重疊構造器( telescoping cons tructor )模式,在這種模式下,提供的第一個構造器隻有必要的參數,第二個構造器有一個可選參數,第三個構造器有兩個可選參數,依此類推,最後一個構造器包含所有可選的參數。

  • 随着參數數目增加,它很快就失去了控制;簡而言之,重疊構造器模式可行,但是當有許多參數的時候,用戶端代碼會很難縮寫,并且仍然較難以閱讀

遇到許多可選的構造器參數的時候,還有第二種代替辦法,即

JavaBeans 模式

在這種模式下,先調用一個無參構造器來建立對象,然後再調用setter 方法來設定每個必要的參數,以及每個相關的可選參數:

因為建構過程被分到了幾個調用中,在建構過程中JavaBean可能處于不一緻的狀态。類無法僅僅通過驗證構造器參數的有效性來保持一緻性。試圖使用處于不一緻狀态的對象,将會導緻失敗。與此相關的另一點不足在于,JavaBeans模式組織了把類做成不可變的可能,這就需要程式員付出額外的努力來確定他的線程安全。

builder模式

既能保證構造器模式那樣的安全性,也能保證JavaBeans模式那麼好的可讀性;
不直接生成想要的對象,而是讓用戶端利用所有必要的參數調用構造器,得到一個builder對象,然後用戶端在builder對象上調用類似于setter方法,設定每個相關的可選參數。最後用戶端調用無參的build方法生成不可變的對象;builder是它建構的類的靜态成員類。
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 {
        // 必要參數
        private final int servingSize;
        private final int servings;
        // 可選參數
        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;//傳回Builder類對象本身,以便把調用連結起來
        }

        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;
    }

    @Override
    public String toString() {
        return "[" +
                "servingSize:" + servingSize +
                ",servings:" + servings +
                ",calories:" + calories +
                ",fat:" + fat +
                ",sodium:" + sodium +
                ",carbohydrate:" + carbohydrate +
                "]";
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)             .calories(100).sodium(35).carbohydrate(27).build();
        System.out.println(cocaCola);
    }
}
           

builder像個構造器一樣,可以對其參數強加限制條件,build()方法可以檢驗這些限制條件,将參數從builder拷貝到對象之後,在對象域進行檢驗。

與構造器相比,builder優勢在于:可以有多個可變參數。

Java中傳統的抽象工廠實作Class對象。用newInstance方法充當build方法的一部分,隐含着問題:newInstance總是試圖調用類的無參構造器,若是不存在也不會收到編譯錯誤。用戶端必須在運作時處理異常,很不友善。

Class.newInstance破壞了編譯時的異常檢查,builder接口彌補了不足;

builder模式的不足:為了建立對象必須先建立它的構造器。

繼續閱讀