静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数.对于这样的类,应该用那种构造器或者静态方法来写?
第一种方式:采用重叠构造器模式,在这种模式下,你提供第一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个有两个可选参数,以此类推,最后一个构造器包含所有可选参数.
这种模式可行,但是当参数非常多的时候,客户端的代码会很难编写,并且难以阅读.有时候,类型相同的参数还会造成这样的问题:如果客户端不小心颠倒了其中两个参数的顺序,编译器不会报错,但是程序在运行时会出现错误.
第二种方法:是JavaBean模式,这种模式下,调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数:
客户端代码:
优点:
创建实例很容易,代码可读性高.
缺点:
JavaBean模式自身有着很严重的缺点.因为构造过程被分到了几个调用中,在构造过程中JavaBean可能处于不一致的状态.使用处于不一致状态的对象,将会导致失败,并且这种错误调试十分困难.
JavaBean模式阻止类变成不可变的可能(见第15条),这需要程序员来确保线程安全.
可以通过手动”冻结”对象,来弥补这些不足,意思是:在对象构造完成或者解冻之前不允许使用.这种方式在实践中很少使用.
还有第三种方式,既能保证重叠构造器那样的安全性,又能保证JavaBean那样的可读性.这就是Builder模式的一种形式.
这种方式,不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builer对象.然后客户端在builder对象上调用类似setter方法,来设置每个相关的可选参数.最后,客户端调用无参的builder方法来生成不可变的对象.例子:
package cn.nieyi.builder;
/**
* 测试Builder模式
* @author Administrator
*
*/
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{
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;
}
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;
}
}
客户端代码:
package cn.nieyi.builder;
public class Client {
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(0).sodium(35).sodium(35).calories(27).build();
}
}
builder像个构造器一样,可以对其参数强加约束条件.builder方法可以检验这些约束条件.
将参数从builder拷贝到对象中之后,并在对象域而不是builder域中检验.如果违反了约束条件,builder方法就应该抛出IllegalStateException.异常信息应该显示出违反了那个约束条件.
对多个参数强加约束条件的另一种方法是,用多个setter方法对某个约束条件必须持有的所有参数进行检查.
与构造器相比,builder的微略优势在于,builder可以有多个可变参数.构造器就像方法一样,只能有一个可变参数.而builder利用单独的方法来设置每个参数,你想要多少个可变参数,它们就可以有多少个,直到每个setter方法都有一个可变参数.
Java中传统的抽象工厂实现是Class对象,用newInstance方法充当build方法的一部分.newInstance方法总是企图调用类的无参数构造器.如果类没有可以访问的无参构造器,也不会受到编译时错误.Class.newInstance破坏了编译时的异常检查.
builder模式的不足:
为了创建对象,必须先创建它的构建器.虽然创建构建器的开销在实践中可能不哪些明显,但是在某些十分注重性能的情况下,可能就成问题了.
Builder模式还比重叠构造器模式更加冗长,因此它只在有很多参数的时候才使用,比如4个或者更多个参数.
简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式是种不错的选择,特别是当大多数参数都是可选的时候.与使用传统的重叠构造器模式相比,使用Builder模式的客户端代码将更易于阅读和编写,构建器也比JavaBean更加安全.