1. 重叠构造器模式
在这种模式下,我们提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器含有所有参数。
public class Person {
private String name; // required
private String sex; // required
private Date date; // required
private String email; // required
private int height; // optional
private String edu; // optional
private String nickName; // optional
private int weight; // optional
private String addr; // optional
public Person(String name, String sex, Date date, String email) {
this(name, sex, date, email, 0);
}
public Person(String name, String sex, Date date, String email, int height) {
this(name, sex, date, email, height, null);
}
public Person(String name, String sex, Date date, String email, int height, String edu) {
this(name, sex, date, email, height, edu, null);
}
public Person(String name, String sex, Date date, String email, int height, String edu, String nickName) {
this(name, sex, date, email, height, edu, nickName, 0);
}
public Person(String name, String sex, Date date, String email, int height, String edu, String nickName, int
weight) {
this(name, sex, date, email, height, edu, nickName, weight, null);
}
public Person(String name, String sex, Date date, String email, int height, String edu, String nickName, int
weight, String addr) {
this.name = name;
this.sex = sex;
this.date = date;
this.email = email;
this.height = height;
this.edu = edu;
this.nickName = nickName;
this.weight = weight;
this.addr = addr;
}
}
使用这种模式创建对象时,存在以下几点不足
- 灵活性很差 : 如果客户端只想创建一个给定姓名,性别,生日,邮箱和体重,那么他将被迫调用如下构造函数,这样无意中就”被迫”设置了他本不想设置的一些参数
public Person(String name, String sex, Date date, String email, int height, String edu, String nickName, int weight) { this(name, sex, date, email, height, edu, nickName, weight, null); } - 代码难以编写与阅读 : 当属性有很多的时候,代码不但看起来很丑陋,而且极易出错。试想,若客户端不小心颠倒了两个参数的顺序,编译器也不会出错,但是在运行时就会出现错误的行为,并且这种错误难以发现
2. JavaBeans模式
这时,我们可能转而求助于JavaBeans模式来避免这些问题,但是同时会带来一些新的问题。 - Setter的存在妨碍的其成为不可变类的可能: 这样,在并发环境下,我们就不得不考虑其线程安全性;
- 代码丑陋且对象易处于不一致状态: 上面创建对象的方式毕竟丑陋,同时由于对象的构造过程分为若干个函数调用,所以容易导致对象处于不一致状态。
Person p2 = new Person(); p2.setName("zck"); p2.setSex("boy"); p2.setDate(new Date()); p2.setEmail("*******@qq.com"); p2.setHeight(179); p2.setEdu("NCU"); p2.setNickName("kang"); p2.setWeight(140); p2.setAddr("武汉市"); System.out.println(p2);
3. Builder模式
使用Builder模式创建复杂对象,既能保证像重叠构造器模式那样的安全性,也能保证像JavaBeans模式那么好的可读性。该模式的内涵是:不直接生成想要的对象,而是让客户端利用所有必要的参数构造一个Bulider对象,然后在此基础上,调用类似于Setter的方法来设置每个可选参数,最后通过调用无参的build()方法来生成不可变对象。一般的,所属Builder是它所构建类的静态成员类
public class Person {
private final String name;// required
private final Boolean sex;
private final Integer age;
private final Integer height;// optional
private final Double weight;
private final Date birthday;
private final String addr;
private Person(Builder builder) {
this.name = builder.name;
this.sex = builder.sex;
this.age = builder.age;
this.height =builder.height;
this.weight = builder.weight;
this.birthday = builder.birthday;
this.addr = builder.addr;
}
public static class Builder {
private final String name;
private final Boolean sex;
private final Integer age;
private Integer height;
private Double weight;
private Date birthday;
private String addr;
public Builder(String name, Boolean sex, Integer age) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 返回Builder对象本身,链式调用
public Builder height(Integer height) {
this.height = height;
return this;
}
public Builder weight(Double weight) {
this.weight = weight;
return this;
}
public Builder birthday(Date birthday) {
this.birthday = birthday;
return this;
}
public Builder addr(String addr) {
this.addr = addr;
return this;
}
// 通过Builder构建所需Person对象,并且每次都产生新的Person对象
public Person build() {
return new Person(this);
}
}
我们可以通过下面的方式来创建一个Person对象
Person.Builder builder = new Person.Builder("老张",true,23);
Person person = builder.addr("武汉市江夏区").birthday(new Date(2008,8,8)).height(179).weight(140D).build();
- Person类的构造方法是私有的: 也就是说,客户端不能直接创建Person对象;
- Person类是不可变的: 所有的属性都被final修饰,在构造方法中设置参数值,并且不对外提供Setter方法
- Bulider模式的高可读性: Builder模式使用了链式调用,可读性更佳。
- Builder对象与目标对象的异同: Person与Buildr拥有共同的属性,并且Builder内部类的构造方法中只接收必传的参数,同时只有这些必传的参数使用final修饰符
4. Builder模式中的参数约束与线程安全行
Person对象是不可变的,因此是线程安全;但是,Builder对象并不具有线程安全性。因此,当我们需要对Person对象的参数强加约束条件时,我们应该可以对builder()方法中所创建出来的Person对象进行校验,即我们可以将builder()方法进行如下重写:
public Person build(){
Person person = new Person(this);
if (!"boy".equals(person.sex)){
throw new IllegalArgumentException("所注册用户必须为男性!");
}else{
return person;
}
}
需要特别主要的是,我们是对Person对象进行参数检查,而不是对Builder对象进行参数检查,因为Builder对象不是线程安全的,即下面的代码存在线程安全问题
public Person build(){
if (!"boy".equals(this.sex)){
throw new IllegalArgumentException("所注册用户必须为男性!");
}else{
return new Person(this);
}
}