1. 程式人生 > 實用技巧 >Item 2: Consider a builder when faced with many constructor parameters

Item 2: Consider a builder when faced with many constructor parameters

場景1:當一個物件有多個引數需要初始化,分為必需引數和可選引數
方法一:

通過對建構函式進行過載來匹配不同情況,但此操作僅適用於引數較少的情況,在有很多引數的情況下,大大降低了程式碼可讀性和健壯性。

方法二:

使用JavaBean模式,該模式下,呼叫無參建構函式來建立物件,隨後通過setter方法來設定各引數。
顯而易見地,使用該方法也就意味著這個類不能是immutable class。

方法三:

使用builder模式,示例如下:

// Builder Pattern
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 {
        // 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 sodium = 0;
        private int carbohydrate = 0;

        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) {
            carbohydrate = 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;
    }
}

大致思路是在類中建立一個內部靜態類Builder,通過Builder中的setter對其屬性(可選引數)進行初始化(Builder中的建構函式通常用來初始化必需的引數),接著由Builder中的build函式呼叫外部類的建構函式並將Builder物件傳入,通過Builder物件中的初始化資訊來初始化外部類的屬性值,最後返回了初始化完畢的外部類物件。

即,外部類的建構函式通過獲取內部類Builder物件(裝載著初始化資料),拿出其中的資料初始化自己的屬性值。

呼叫如下:

NutritionFacts nutritionFacts = new NutritionFacts.Builder(240,8)
                .calories(100)
                .sodium(35)
                .carbohydrate(27)
                .build();
場景2:繼承出現在builder模式中

要注意不同層級的builder要返回對應的class,abstract類的builder返回值也是abstract的,實體類的builder也是實體的。同時要注意,為了限制返回型別為子類,通常採用遞迴泛型的方法。如下:

public abstract class Pizza {
    public enum Topping {
        HAM,
        MUSHROOM,
        ONION,
        PEPPER,
        SAUSAGE
    }

    final Set<Topping> toppings;

    abstract static class Builder<T extends Builder<T>> {
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);

        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }

        abstract Pizza build();

        protected abstract T self();

    }

    Pizza(Builder<?> builder) {
        toppings = builder.toppings.clone();
    }
}

注意到抽象類內部類Builder中的<T extends Builder>,這保證了返回型別T是Pizza的子類。在子類繼承Pizza後,內部類中的build方法宣告的返回型別是子類自己,這被稱為協變,用一句話說就是,子類方法宣告的返回型別可以是超類中宣告的返回型別的子型別。協變例子如下:

    public static class Builder extends Pizza.Builder<Builder> {
        private final Size size;

        public Builder(Size size) {
            this.size = Objects.requireNonNull(size);
        }

        @Override
        public NyPizza build() {
            return new NyPizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }