来自《effective+java中文版》:
静态工厂和构造器有一个共同的局限性,都不能很好的扩展到大量的可选参数。
重叠构造器模式
如果我们有一个类,表示一个用户的个人信息,有些值是必须的,有些不是必须的。
必须的参数:用户名、年龄、性别
非必须的参数:身高、住址、体重
public class User {
private String username;
private int age;
private int sex;
private float height;
private String address;
private float weight;
public User(String username,int age,int sex){
this(username,age,sex,0);
}
public User(String username,int age,int sex,float height){
this(username,age,sex,height,"");
}
public User(String username,int age,int sex,float height,String address){
this(username,age,sex,height,address,0f);
}
public User(String username,int age,int sex,float height,String address,float weight){
this.username = username;
this.age = age;
this.sex = sex;
this.height = height;
this.address = address;
this.weight = weight;
}
}
当你想要创建实例的时候,利用参数列表最短的构造器,该列表包含了要设置的参数:
User u = new User("china", 23, 1, 55.5f, "",179.2f);
这个构造器调用通常许多你不想设置的参数,但还是不得不为他们传值。随着属性列表的增多,很快就会失去控制。
重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难写,并且难以阅读。
如果读者想知道某一个参数是什么意思,还需要仔细的数着参数个数来叹究竟。可能会导致微妙的错误,很可能一不小心两个参数位置颠倒了,微妙的错误也可能影响极大的后患。
遇到许多构造器参数的时候,还有一种javabbeans模式,这种模式只需要一个午餐的构造方法,然后通过setter方法给每个必要的参数和不必要的参数进行赋值:
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
这种模式弥补了重叠构造器模式的不足,创建实例容易,代码也易读。
但是,javabeans模式本身有着很严重的缺点,因为构造过程被分到几个调用中,构造过程中可能出现javabean不一致的状态,类无法仅仅通过构造器参数的有效性来保证一致性,试图使用处于不一致的状态,将会导致失败。javabean模式阻止了把类做成不可变的可能。程序员需要通过额外的努力来保证线程的安全。
幸运的是,还有第三种方法,既能保证像重叠器模式那样的安全性,也能保证javabeans模式那样的可读性,这就是Builder模式的一种形式。不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器,得到一个builder对象,然后客户端在builder对象上调用类似setter的方法,来设置每个相关的可选参数。最后客户端调用无参的builder方法生成不可变的对象。实例:
public class User {
private String username;
private int age;
private int sex;
private float height;
private String address;
private float weight;
public User(Builder builder) {
username = builder.username;
age = builder.age;
sex = builder.sex;
height = builder.height;
address = builder.address;
weight = builder.weight;
}
public static class Builder {
// required
private String username = "";
private int age = 0;
private int sex = 1;
// optional
private float height = 0.0f;
private String address = "";
private float weight = 0.0f;
public Builder(String username, int age, int sex) {
this.username = username;
this.age = age;
this.sex = sex;
}
public Builder height(float val) {
height = val;
return this;
}
public Builder address(String val) {
address = val;
return this;
}
public Builder weight(float val) {
weight = val;
return this;
}
public User build() {
return new User(this);
}
}
public String getUsername() {
return username;
}
public int getAge() {
return age;
}
public int getSex() {
return sex;
}
public float getHeight() {
return height;
}
public String getAddress() {
return address;
}
public float getWeight() {
return weight;
}
}
调用代码:
public class TestBuilder {
public static void main(String[] args) {
User user = new User.Builder("china",23,1).address("hangzhou").build();
System.out.println(user.getUsername()+","+user.getAge()+","+user.getSex()+","+user.getAddress());
}
}
打印出:
china,23,1,hangzhou。
同样,还可以设置更多参数:
User user = new User.Builder("china",23,1).address("hangzhou").height(1.78f).build();
user.getHeight()可以获取到:1.78
这样客户端代码更容易编写,更易于阅读。