当创建对象 构造函数有多个参数时,可以考虑用构建器方式

静态工厂和构造函数都有一个局限:它们不能对大量可选参数做很好的扩展。以一个类为例,它表示包装食品上的营养标签。这些标签上有一些字段是必需的,如:净含量、毛重和每单位份量的卡路里,另有超过 20 个可选的字段,如:总脂肪、饱和脂肪、反式脂肪、胆固醇、钠等等。大多数产品只有这些可选字段中的少数,且具有非零值。

应该为这样的类编写什么种类的构造函数或静态工厂呢?传统的方式是使用可伸缩构造函数,在这种模式中,只向构造函数提供必需的参数。即,向第一个构造函数提供单个可选参数,向第二个构造函数提供两个可选参数,以此类推,最后一个构造函数是具有所有可选参数的。这是它在实际应用中的样子。为了简洁起见,只展示具备四个可选字段的情况:

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 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。只有六个参数时,这可能看起来不那么糟,但随着参数的增加,它很快就会失控。简单地说,可伸缩构造函数模式可以工作,但是当有很多参数时,编写客户端代码是很困难的,而且读起来更困难

当你在构造函数中遇到许多可选参数时,另一种选择是 JavaBean 模式,在这种模式中,你调用一个无参数的构造函数来创建对象,然后调用 setter 方法来设置每个所需的参数和每个感兴趣的可选参数

public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize = -1; // Required; no default value
    private int servings = -1; // Required; no default value
    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 模式的可读性。它是建造者模式的一种形式 [Gamma95]。客户端不直接生成所需的对象,而是使用所有必需的参数调用构造函数(或静态工厂),并获得一个 builder 对象。然后,客户端在构建器对象上调用像 setter 这样的方法来设置每个感兴趣的可选参数。

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;
    }
}
复制代码

NutritionFacts 类是不可变的,所有参数默认值都在一个位置。构建器的 setter 方法返回构建器本身,这样就可以链式调用,从而得到一个流畅的 API。下面是客户端代码的样子:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享