这算是本人的第一篇博文了,其实好久之前就想写了,但是苦于肚内羞涩,也没有什么想要表达的,于是今天鼓足勇气,在大家面前献丑了。
今天看了一下<<Effective Java>>第二版,总结一下第一章的第二条知识点,希望对大家有所帮助,也巩固一下自己对知识点的掌握。
好了开始了,大家设想一下一个场景,假设有一个学生类(Student),属性有姓名(name),学号(stuno),年龄(age),性别(gender),出生日期(birthday),学校(school),年级(grade),班级(classes),家庭住址(address),电话号码(tel),邮箱(email)等等,其中姓名和学号是必须的,而其他的是可选的,如果按照一贯的方式我们会采用**重叠构造器模式**,那么将会有如下的代码:
public class Student{
private String name;
private String stuno;
private int age;
private int gender;//0:女,1:男
private Date birthday;
private String school;
private String grade;
private String classes;
......
//必须的构造器
public Student(String name,String stuno){
this.name = name;
this.stuno = stuno;
}
//可选的构造器,在这里只写三个
public Student(String name,String stuno,int age){
this.name = name;
this.stuno = stuno;
this.age = age;
}
public Student(String name,String stuno,int age,Date birthday){
this.name = name;
this.stuno = stuno;
this.age = age;
this.birthday = birthday;
}
public Student(String name,String stuno,String school){
this.name = name;
this.stuno = stuno;
this.school = school;
}
那么以上的代码有什么缺点吗?
1、如果属性过多,那么构造函数的参数就越多,因此客户端就很难编写代码,有很大的可能造成参数值与参数名称匹配错误。
2、当你仅仅需要其中的几个属性来创建对象时,那么你就不得不去写很多的构造函数来满足你的需求的变化,这样你的代码过于庞大,且很难维护。当然,也许你会说,我们可以只创建一个构造函数,将所有的属性全部注入,但是这样真的好吗?
这样就会产生一个新的问题,当你仅仅使用其中的几个属性的时候,其他的你原本是不想设值得,但是你不得不为他们传递值。
也许你可以想到第二种解决方法,即使用JavaBeans模式,在这种模式下,调用一个无参的构造器创建对象,然后调用setter方法为每个必要的属性设值:
public class Student{
private String name;
private String stuno;
private int age;
private int gender;//0:女,1:男
private Date birthday;
private String school;
private String grade;
private String classes;
......
public Student(){}
public void setName(String name){
this.name = name;
}
public void setStuno(String stuno){
this.stuno = stuno;
}
public void setAge(int age){
this.age = age;
}
.....
}
这种模式弥补了重叠构造器模式的不足,设计和使用都比较简单:
Student student = new Student();
student.setName("aaa");
student.setStuno("A112");
student.setAge(23);
....
遗憾的是,JavaBeans模式自身有着严重的缺点。
1、因为构造过程被分到了几个调用中,在构造中JavaBean可能处于不一致状态。这样,类就无法仅仅通过检验构造器参数的有效性来保证一致性。
2、JavaBeans模式阻止了把类做成不可变的可能,这样就需要程序员付出额外的努力来保证它的线程安全。
幸运的是,还有第三种方法,既能保证像重叠构造器模式那样的安全性,又能保证像JavaBeans模式那样好的可读性,这就是Builder模式的一种形式,不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或静态工厂),得到一个Builder对象。然后客户端在Builder对象上调用类似于Setter的方法,来设置每个相关的可选参数,最后,客户端调用无参的builder方法来生成不可变的对象。这个Builder是它构建类的静态成员类。代码如下:
public class Student{
private String name;
private String stuno;
private int age;
private int gender;
private Date birthday;
private String school;
.....
public static class Builder{
//必须的属性参数
private final String name;
private final String stuno;
//可选的属性参数:需要设置初始值
private final int age = 0;
private final int gender = 1;
private final Date birthday = null;
.....
public Builder(String name,String stuno){
this.name = name;
this.stuno = stuno;
}
public Builder setAge(int age){
this.age = age;
return this;
}
public Builder setGender(int gender){
this.gender = gender;
return this;
}
public Builder setBirthday(Date birthday){
this.birthday = birthday;
return this;
}
public Student build(){
return new Student(this);
}
}
public Student(Builder builder){
name = builder.name;
stuno = builder.stuno;
age = builder.age;
gender = builder.gender;
......
}
}
注意:Student是不可变的,所有的默认参数值都单独放在一个地方。builder的setter方法返回builder本身,以便可以把调用链接起来。
Student student = new Student("DARKER","A1112").
setAge(22).setGender(1).setBirthday(null).build();
总结:
1、builder像个构造器一样,可以对其参数强加约束条件,build方法可以检验这些约束条件,将参数从builder拷贝到对象之中,并在对象域并不是builder域中对其检验,如果违反了任何的约束,build方法就会抛出IllegalStateException异常。
2、对多个参数加强约束条件的第二种方式,用多个setter方法对某个约束条件所必须有的参数进行检查。如果约束条件没有满足,setter方法就会抛出IllegalArgumentException异常。这样有个好处,一旦传递了无效的参数,立即就会发现约束条件失败,而不必等到调用build方法。
Builder模式也有其不足之处。
1、为了创建对象,必须先创建它的构建器。
2、Builder模式还比重叠构造器模式更加冗长,因此它只在有很多参数的时候使用。
但是记住,通常最好一开始就使用构建器,避免了以后可能只增加参数所带来的麻烦。
简而言之,如果类的构造器或者静态工厂中具有多个参数设计这种类时,Builder模式就是中不错的选择。