静态工厂和构造器有个共同的的局限性:它们都不能很好的扩展大量可选的参数。举个栗子:一个饮料类,不同的饮料,包含相同的参数(水,卡路里,脂肪量,糖量……),也有不同的参数,有的含有矿物质,胡萝卜素 , 钠 , XX酸……对于这样的类,我们该如何设计构造器?
传统构造器的困境
对于传统的构造器,有多少个值域就要去写多少个构造器。比如饮料公共参数是8个,那么至少要写8个构造器出来,可选参数从1个到8个。
public class Beverage{
private final double volume;//单位:ml
private final double calories;//卡路里
private final double fat;//脂肪
private final double sugar;//糖
private final double sodium;//钠
private final double Protein;//蛋白质
public Beverage(double volume){
this.volume = volume;
}
public Beverage(double volume,double calories){
this.volume = volume;
this.calories = calories;
}
public Beverage(double volume,double calories,double fat){
this.volume = volume;
this.calories = calories;
this.fat = fat;
}
.
.
.
.
.
.
}
随着参数的增加,这样的构造器将随着增加。然而按照上面的构造器,有些构造器的参数,获取用不到。比如无糖可乐,它没有suger这个参数,有其他5个参数。那么你需要选用参数最多参数的那个构造器,并且设置suger=0.或许读者会说那就去掉suger,用其他5个参数,在写一个构造器。不是没往这方面想过,你可以试想一下,6个参数,创造所有类型的构造器的个数,是6个阶乘。随着参数个数增加,这个数目是很可怕的,想想要写那么多构造器,都觉得这简直要命啊。说好的优雅呢?
JavaBeans模式
在这种模式下,我们只需要调用一个默认无参的构造器来创建对象,然后通过 setter 方法来设置必要参数或者可选参数的值:
public class Beverage{
private final double volume = 0;//单位:ml
private final double calories = 0;//卡路里
private final double fat = 0;//脂肪
private final double sugar = 0;//糖
private final double sodium = 0;//钠
private final double Protein = 0;//蛋白质
public void setVolume(double volume){this.volume = volume;}
public void setCalories(double calories){this.calories= calories;}
public void setFat (double fat ){this.fat = fat ;}
public void setSugar (double sugar ){this.sugar = sugar ;}
public void setSodium (double sodium ){this.sodium = sodium ;}
public void setProtein (double Protein ){this.Protein = Protein ;}
}
用JavaBeans 模式创建实例非常方便,代码易读性很高:
Beverage beverage = new Beverage(); //----------------->N
beverage.setVolume(500); //----------------->A
beverage.setCalories(0); //----------------->B
beverage.setFat(50); //----------------->C
beverage.setSugar (50); //----------------->D
但是JavaBeans模式有个缺点,它阻止了吧类做成不可变的的可能。简单的说,就是,javaBeans模式下,类的实例,对象很容易就被改变。如上面的5行代码,N :通过调用无参构造器,生成了一个空对象beverage。但是从N开始到D,对象beverage 一直在变化。每一次调用setter 方法,就是在改变beverage 的值域。你无法保证,在D操作之后没有人再去调用setter方法,所以要保证我们一直操作的是D操作后的 beverage,我们要给对象beverage “上锁”,这一段时间,谁也不要来动我的beverage。这就需要我们付出额外的工作来保证对象beverage 的一致性。
有没有办法,保持创建对象可读性高,简单同时还保证 对象的一致性呢?方法是有的,那就是Builder模式。它不直接生成想要的对象,而是通过调用构造器,得到一个builder对象,然后由builder对象调用类似setter方法来设置可选参数。最后,通过无参的build方法生成一个不可变对象,而Builder 类 是我们设计类的静态成员类。
public class Human {
//三个必要参数
private final String name;
private final int age ;
private final String sex ;
//四个可选参数
private final String country;
private final double height;
private final double weight;
private final String bloodType;
//静态成员类--Builder
public static class Builder{
private final String name;
private final int age ;
private final String sex ;
private String country = "";
private double height = 0.0;
private double weight = 0.0;
private String bloodType = "A";
public Builder(String name,int age,String sex){
this.name = name;
this.age = age;
this.sex = sex;
}
public Builder country(String countryName){
country = countryName;
return this;
}
public Builder height(double heightVal){
height = heightVal;
return this;
}
public Builder weight(double weightVal){
weight = weightVal;
return this;
}
public Builder bloodType(String bloodTypeVal){
bloodType = bloodTypeVal;
return this;
}
public Human build(){
return new Human(this);
}
}
//通过builder来给对象human设置参数
private Human(Builder builder){
name = builder.name;
age = builder.age;
sex = builder.sex;
country = builder.country;
height = builder.height;
weight = builder.weight;
bloodType = builder.bloodType;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getSex() {
return sex;
}
public String getCountry() {
return country;
}
public double getHeight() {
return height;
}
public double getWeight() {
return weight;
}
public String getBloodType() {
return bloodType;
}
@Override
public String toString() {
StringBuffer str = new StringBuffer();
str.append(" name="+name.toString());
str.append(",age="+String.valueOf(age));
str.append(",sex="+String.valueOf(sex));
str.append(",country="+String.valueOf(country));
str.append(",height="+String.valueOf(height));
str.append(",weight="+String.valueOf(weight));
str.append(",bloodType="+String.valueOf(bloodType));
return super.toString()+str.toString();
}
public static void main(String[] args) {
Human chinese = new Human.Builder("David", 25, "man").bloodType("A").country("China").build();
System.out.println(chinese.toString());
chinese.country.toString();
}
}
如上,类Human 值域是final,所以我们无法在外部像JavaBeans模式调用setter来给对象设置参数,也没有传统的构造器,这样就保证了我们之前所说的一致性。
Human chinese = new Human.Builder("David",25,"man").bloodType("A").country("China").build();
我们通过调用静态成员类Builder的构造器,给builder设置了三个必要参数,然后调用方法设置可选参数,方法名与参数名一致这样代码的可读性就高了,最后通过调用Builder的无参构造器去创建Human 的对象,并把相关参数传递到chinese中,这样就完成了。随后的测试中可发现,这个chinese 是无法改变的。在满足高可读性,方便性,一致性中,Builder模式为我们给出来选择。
是不是一定要用Builder模式,这就要根据实际场景来了。非必要,JavaBeans是第一选择,正如在工作中,大多数时候使用的就是JavaBeans模式,如遇到对对象一致性要求高时,那就愉快用Builder模式吧。