假设有一个场景,对于一个类在构建时,其中的一些成员变量需要进行初始化(当然可能会有默认的值)。我们假设这个类中的成员变量有一些是必须在初始化的时候进行赋值的,还有一些是可选的,解决方案有以下几种:
-
重叠构造器:提供一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器包含所有的可选参数,例子如下:
/** * @author Liufeng * Created on 2018年9月16日 上午11:32:29 */ public class NutritionFacts { private final int servingSize; // required private final int servings; // required private final int calories; // optional private final int fat; // optional private final int sodium; // optional private final int carbohydrate; // 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; } }
重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。
- JavaBeans模式。通过一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数。
遗憾的是,JavaBeans模式有着很严重的缺点,因为构造过程被分到几个调用中,在构造过程中JavaBeans可能处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。另外,JavaBeans模式阻止了把类做成不可变的可能,这就需要程序员付出额外的努力来确保它的线程安全。
- Builder模式。不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象,然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后客户端调用无参的builder方法来生成不可变的对象。这个builder是它构建的类的静态成员类。示例如下:
/** * @author Liufeng * Created on 2018年9月16日 上午11:32:29 */ public class NutritionFacts { private final int servingSize; // required private final int servings; // required private final int calories; // optional private final int fat; // optional private final int sodium; // optional private final int carbohydrate; // optional 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 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; } 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; } }
注意NutritionFacts是不可变的,所有的默认参数都单独放在一个地方。builder的setter方法返回builder本身,以便可以把调用连接起来。
NutritionFacts cocala = new NutritionFacts.Builder(240, 8) .calories(100).sodium(35).carbohydrate(27).build();
这样的客户端代码很容易编写,可以对其参数强加约束条件,build方法检验这些约束条件。将参数从build方法拷贝到对象中之后,在对象域中而不是在builder域中对他们进行检验。