有时,我们在写一个构造函数时,经常因为它包含众多的参数而苦恼,这时可以考虑用Builder模式来创建对象。
如,我们要设计一个营养成份的类,包含能量,蛋白质,脂肪,钙,铁,锌,维生素A, 维生素B1 ... 等,但在构造的时候,不一定每次都需要这些参数,如钙,铁,锌和维生素等是可选的,为了适应多种可能的搭配,比较原始的办法就是采用telescoping constructor模式,例子如下。
public class Nutrition
{
private int calories;
private int protein;
private int fat;
private int ca;
private int fe;
private int Va;
private int Vb;
...
public Nutrition(int cal, int pro){...}
public Nutrition(int cal, int pro, int fat){...}
public Nutrition(int cal, int pro, int fat, int ca){...}
public Nutrition(int cal, int pro, int fat, int ca, int fe){...}
public Nutrition(int cal, int pro, int fat, int ca, int fe, int ){...}
public Nutrition(int cal, int pro, int fat, int ca, int fe, int Va){...}
.......
}
这种方法的缺点很明显,一个函数的参数一旦超过3个,用户就很容易把顺序搞混,而更杯具的是这种情况编译器无法识别,非常不易查错。
第二种方法是JavaBean模式。
public class Nutrition
{
private int calories;
private int protein;
private int fat;
private int ca;
private int fe;
private int Va;
private int Vb;
...
public Nutrition(){...}
public setCal(int cal);
public setPro(int pro);
public setfat(int fat);
public setca(int ca);
public setfe(int fe);
.......
}
这种方式的缺点是在整个构造对象的过程中,其状态不是一致的(inconsistent state),即在创建好一个对象后,这个对象的状态在后面的某个时候内仍然是在变化的(因为其值发生了改变),此外,这种方法只能构造一个可变(mutable)对象,必须采用附加的方法保证线程安全。
所以,书中推荐了第三种方法利用Builder来构造。
public class Nutrition
{
private final int calories;
private final int protein;
private final int fat;
private final int ca;
private final int fe;
private final int Va;
private final int Vb;
public static class Builder {
private final int calories; //必有参数
private final int protein; //必有参数
private final int fat; //可选参数
private final int ca; //可选参数
private final int fe; //可选参数
private final int Va; //可选参数
private final int Vb; //可选参数
public Builder(int cal,int pro) {...};
public Builder fat(int fat){...};
public Builder ca(int ca){...};
public Builder fe(int fe){...};
public Nutrition builder(Builder builder)
{
return new Nutrition(this);
}
} //end of Builder
private Nutrition(Builder builder)
{
calories = builder.calories;
protein = builder.protein;
ca = builder.ca;
fe = builder.fe;
....
}
}
使用时可以采用以下代码:
Nutrition nu = new Nutrition.Builder(1,2).fat(45).ca(456).fe(4).builder();
其中,Builder()中的是必选参数,其他的是可选参数。
采用这种方式,代码易读易写,参数初始化的顺序也无关紧要。
当然,它也有缺点。它必须先构造一个Builder对象,其开销在性能问题很关键的场合的是不适用的。另外,相比之下,Builder模式比前面现两种模式更加复杂,如果不是有太多的参数的话,就没有必要使用这种模式。