问题提出
假如一个类有很多域,域中有一些必需参数和大量可选参数,那么静态工厂和构造器需要提供一个只含必要参数的构造器,然后第二个构造器需要有一个可选参数,第三个有两个可选参数,依次类推,最后一个构造器包含所有可选参数。
这样重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且较难阅读。
解决方案
- 使用JavaBeans模式
即调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数和需要的可选参数。
这样创建实例很容易,并且代码也容易理解阅读。But,构造过程被分到了几个调用中,而不是一下子就完成,这样在构造过程中JavaBean可能处于不一致状态,可能会导致错误!有时候可以通过手工“冻结”对象来解决这一问题,但是实践中比较少用。另外JavaBean使得类不能做成不可变的,这样程序员要付出额外努力确保线程安全。 - 使用Builder模式
这种方法不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或静态工厂),得到一个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 {
//compulsory paras
private final int servingSize;
private final int servings;
//optional paras - 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 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;
}
}
builder的setter返回builder本身,这样调用可以链接起来。
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
.calories(100)
.sodium(35).build();
利用build方法进行约束条件检验,将参数从builder拷贝到对象中之后,在对象域而不是builder域对他们进行检验。若违反约束条件,build方法应该抛出IllegalStateException,并告知违反哪个约束条件。
另一种检验情况实在builder中的setter方法中检验。
Builder模式也有不足,比如创建构造器会造成额外的开销,并且Builder模式还比重叠构造器模式更加冗长。
总结
如果类的构造器或静态工厂中具有多个参数,特别是大多数参数都是可选的时候,Builder模式是种不错的选择。这样客户端代码将更容易阅读和编写,构造器也比JavaBeans更加安全。