本文是《Effective Java》读书笔记的第二条。
废话不多说,先上个例子吧。我们一开始学程序开发,写完Hello world之后,通常就开始跟student较劲了。。。
学生通常有多个属性,比如学号、姓名、性别、年龄、年级、邮箱、家庭住址等等。
那么问题来了,在创建学生对象的时候若要初始化不同的参数,得弄多少个构造方法啊,这恐怕是个排列组合问题。如果用前一篇介绍到的静态工厂方法也比较麻烦。
通常大家会采用层叠构造器,如下(我真的不是为了凑字数。。。)
public class Student {
private String id;
private String name;
private String sex;
private int age;
private String grade;
private String email;
private String addr;
public Student() {
}
public Student(String id) {
this(id, null);
}
public Student(String id, String name) {
this(id, name, null);
}
public Student(String id, String name, String sex) {
this(id, name, sex, 0);
}
public Student(String id, String name, String sex, int age) {
this(id, name, sex, age, null);
}
public Student(String id, String name, String sex, int age, String grade) {
this(id, name, sex, age, grade, null);
}
public Student(String id, String name, String sex, int age, String grade,
String email) {
this(id, name, sex, age, grade, email, null);
}
public Student(String id, String name, String sex, int age, String grade,
String email, String addr) {
super();
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
this.grade = grade;
this.email = email;
this.addr = addr;
}
}
那么问题有来了,我只想初始化姓名和邮箱用哪个?还有这么多构造器都看乱套了,顺序一不小心就搞错,有木有其他办法?
还有一种办法就是采用JavaBeans模式,先用无参构造方法来创建对象,然后调用setter方法来依次设置每个需要设置的值。
嗯~问题解决了嘛,等等,好像有什么不妥~~~
- 由于几个参数的setter过程是分离的,因此在构造过程中有可能处于不一致的状态;
- 如果对象是不可变的话,就无法使用setter的方式了。
铺垫了这么多,还是赶紧清楚本文主角——Builder模式吧。
使用构建器Builder,你可以这样创建对象:
Student stu = new Student.Builder("20150102", "Tom").sex("male").age(12)
.grade("Grade 1").email("tom@163.com").addr("铁岭鸟不拉屎村").builder();
怎么样,是不是清晰了很多,而且规避了刚才JavaBeans方式的两个问题。那么如何实现刚才的效果呢?
public class Student {
private String id;
private String name;
private String sex;
private int age;
private String grade;
private String email;
private String addr;
public static class Builder {
// 必须初始化的属性
private String id;
private String name;
// 可选属性
private String sex;
private int age;
private String grade;
private String email;
private String addr;
public Builder(String id, String name) {
this.id = id;
this.name = name;
}
public Builder sex(String sex) { this.sex = sex; return this; }
public Builder age(int age) { this.age = age; return this; }
public Builder grade(String grade) { this.grade = grade; return this; }
public Builder email(String email) { this.email = email; return this; }
public Builder addr(String addr) { this.addr = addr; return this; }
public Student builder() {
return new Student(this);
}
}
public Student(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.sex = builder.sex;
this.age = builder.age;
this.grade = builder.grade;
this.email = builder.email;
this.addr = builder.addr;
}
}
不用我多说,从刚才的代码你首先感觉到的就是高度的灵活性,此外:
- Builder可以对其参数添加约束条件,也就是在build()方法中进行检验;
- Builder可以自动填充某些域,比如每次创建对象时自动增加序列号;
- 这种方式可以实现抽象工厂,比如下边的接口,刚才代码里的
Student.Builder
其实就是Builder<Student>
的实现;相对于Class.newInstance()
这种Java中传统的抽象工厂实现,有如下优点
newInstance()
方法总是调用类的无参构造方法,当然也许不存在无参构造方法,这时候并没有编译错误;- 对于
newInstance()
方法客户端必须处理InstantiationException或者IllegalAccessException,而且newInstance()
方法还会传递无参构造器抛出的异常,即使它本身缺乏相应的throws子句,因此它实际上破坏了编译时的异常检查。
public interface Builder<T> {
public T buile();
}
当然Builder模式也有不足:
- 为了创建对象必须先创建它的构建器,在某些非常注重性能的情况下,可能其开销就是问题了;
- Builder模式相对来说,代码量更多,所以通常只有在很多参数的时候才使用,尤其是大多数参数都是可选的时候。