构建者模式用于构造对象,适合于:当构造对象时需要大量的可选参数。在这方面静态工厂方法和构造器都不是很擅长,因为在这种情况下原本不想设置的参数,也必须传递值。随着参数的增加,这种问题会更加严重,尤其是当参数中包含相同的数据类型时,使用者必须详细阅读API才能防止误传参数。
对于大量可选参数的情况,通常习惯使用重叠构造器或者是使用JavaBeans模式。
场景:考虑使用一个类表示食品的营养成分标签。包含如下几个必需域:每份的含量、每罐的含量以及每份的卡路里;还包含多个可选域:总脂肪量、胆固醇、钠等。
1.重叠构造器
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; } public static void main(String[] args) { NutritionFacts cocaCola=new NutritionFacts(240,8,100,0,0,27);//这里第4,5个参数不需要设置,但必须传入0 } }
这里,提供一个只有必需参数的构造器,其他的构造器在此基础上增加一个、两个。。。可选参数,通过构造器链进行参数的设置。当创建对象时,利用参数列表最短的构造器即可。但这些参数中有可能包含一些跟不想设置的参数,必须传入默认值。当参数列表包含连续的相同类型的参数时,如果客户端不小心颠倒了参数的顺序,编译器是不会发现的,只有在运行时才会出错。
2.JavaBeans
采用传统JavaBeans模式,调用无参构造器实例化一个对象,然后调用需要的setter方法进行参数的设置,这样构造过程被放在了多个调用中,很可能使对象处于不一致的状态。而且还有一点就是因为setter方法的存在,JavaBeans不能实现对象的不可变性,客户端可以调用setter方法改变对象的状态。
3.Builder模式
Builder模式用于封装一个产品的构造过程,并允许按步骤实现。运用这种方法,可以具有JavaBeans的可读性,并且可以像使用构造器或者静态工厂方法那样产生不可变对象。客户端利用必需的参数调用构造器或静态工厂方法,得到一个Builder对象。然后再在Builder对象调用类似JavaBeans的setter方法进行可选参数的设置,这样构造一个对象所需的参数都保存在Builder对象中,然后用Builder对象实例化一个不可变对象。
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(Builder builder){ this.servingSize=builder.servingSize; this.servings=builder.servings; this.calories=builder.calories; this.fat=builder.fat; this.sodium=builder.sodium; this.carbohydrate=builder.carbohydrate; } //作为嵌套类实现,这样可以保证每个类都可以有一个Builder 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 carbohydrate=0; //可选 public Builder(int servingSize,int servings){ this.servingSize=servingSize; this.servings=servings; } //返回Builder是为了可以将调用连接起来 public Builder calories(int c){ this.calories=c; return this; } public Builder fat(int f){ this.fat=f; return this; } public Builder carbohydrate(int c){ this.carbohydrate=c; return this; } public Builder sodium(int s){ this.sodium=s; return this; } public NutritionFacts build(){ return new NutritionFacts(this); } } public static void main(String[] args) { NutritionFacts cocaCola=new NutritionFacts.Builder(240,8).calories(100).carbohydrate(27).build(); } }
与构造器相比,Builder的优势在于它可读性更强,并且可以具有多个可变参数。因为每个可选参数的设置方法都可以编程相应的可变参数,而构造器只能有一个。还有就是Builder更加灵活,可以用一个Builder对象实例化多个对象。
当然由于在构造对象时需要先实例化Builder对象,这样会带来一些性能损耗。不过当有多个可选参数时,Builder模式提供的灵活性、可读性和安全性更好,是一个不错的选择