静态工厂和构造器有个共同的局限性:它们都不能很好的扩展到大量的可选参数。
例如:用一个类包装食品外面显示的营养成份标签。这些标签有几个域是必需的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量胆固醇、钠等等。
对于这样的类,如果用重叠构造器模式,则需要创建许多拥有不同参数的构造器。下面举一个例子,这个食品有两个必选参数,四个可选参数。
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 carbohudrate; //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 carbohudrate){
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohudrate = carbohudrate;
}
}
现在根据这个类创建一个实例:NutritionFacts cacaCola = new NutritionFacts(240,8,100,0,35,27);
这个构造器调用通常需要许多你本不想设置的参数,但还是不得不为它们传递值。随着参数数目增多,客户端代码会越发难写,越发难以阅读。
当有许多参数时,还有一种替代方法,即JavaBean模式。在这种模式下,调用一个无参构造器创建对象,然后调用setter方法设置每个必要的参数。
public class NutritionFacts {
private int servingSize = -1; //required:no default value
private int servings = -1; //required:no default value
private int calories = 0; //optional
private int fat = 0; //optional
private int sodium = 0; //optional
private int carbohudrate = 0; //optional
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 setCarbohudrate(int val){carbohudrate = val;}
}
创建实例:NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohudrate(27);
JavaBean模式也有很大的缺点,因为构造过程被分割成几个调用函数,在构造过程中JavaBean可能处于不一致状态。
最后介绍第三种方法,这就是Builder模式的一种形式。不直接生成想要的对象,然后客户端在builder对象上调用类似于setter的方法,
来设置每个相关的可选参数,最后调用无参的build方法生成不可变对象。
public class NutritionFacts {
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohudrate = builder.carbohudrate;
}
private int servingSize; //required
private int servings; //required
private int calories; //optional
private int fat; //optional
private int sodium; //optional
private int carbohudrate; //optional
public static class Builder{
private final int servingSize;
private final int servings;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohudrate = 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 carbohudrate(int val){
carbohudrate = val;
return this;
}
public NutritionFacts build(){
return new NutritionFacts(this);
}
}
}
创建实例:NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohudrate(27).build();
注意: Builder模式也有自己的不足,为创建对象,必须先创建它的构造器,在十分注重性能的情况下,要慎重使用。
Builder模式比重叠构造器模式更加冗长,只有在很多参数时才使用。