使用builder模式创建对象

本文是 Effective Java 第二章的读书笔记,Java使用Builder模式创建对象。

静态工厂和构造器的局限性

面对可能存在多个可选的参数时,不容易扩展。
以构造器为例,通常构建多个参数的对象时,构造器模式使用多个构造器重叠实现。

加入我们需要一个收件人的对象,他的姓名和地址是必填参数,年龄性别假名职业工资等是可选参数,则创建如下对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Receiver {
private String name;
private String address;
private Integer age;
private String gender;
private String nickname;
private String occupation;
private Double salary;
public Receiver(String name, String address) {
this(name, address, 0);
}
public Receiver(String name, String address, Integer age) {
this(name, address, age, null);
}
public Receiver(String name, String address, Integer age, String gender) {
this(name, address, age, gender, null);
}
public Receiver(String name, String address, Integer age, String gender, String nickname) {
this(name, address, age, gender, nickname, null);
}
public Receiver(String name, String address, Integer age, String gender, String nickname, String occupation) {
this(name, address, age, gender, nickname, occupation, null);
}
public Receiver(String name, String address, Integer age, String gender, String nickname, String occupation, Double salary) {
this.name = name;
this.address = address;
this.age = age;
this.gender = gender;
this.nickname = nickname;
this.occupation = occupation;
this.salary = salary;
}
public void selfIntroduce() {
System.out.println(
"姓名:" + name + ",地址:" + address + ",年龄:" + age + ",性别:" + gender + ",称号:" + nickname + ",职业:" + occupation + ",工资:" + salary);
}
}

从代码可以看出,构造器已经太多。如果后期还要加入其他的信息如学历等,估计程序猿要疯掉。。。显然,这样的代码冗长而且不容易拓展。
我们new两个对象出来,并分别调用它们的selfIntroduce()方法:

1
2
3
4
5
Receiver likui = new Receiver("李逵","梁山",25,"m","黑旋风","杀手",10000.00);
Receiver ligui = new Receiver("李逵","梁山",25,"m","杀手","黑旋风",10000.00);
likui.selfIntroduce();
ligui.selfIntroduce();

控制台的输出结果:

姓名:李逵,地址:梁山,年龄:25,性别:m,称号:黑旋风,职业:杀手,工资:10000.0
姓名:李逵,地址:梁山,年龄:25,性别:m,称号:杀手,职业:黑旋风,工资:10000.0

可以看出,第一条是没问题的,但第二条记录的职业和假名弄反了。
这也是构造器模式的一个弊端:参数很容易弄混掉。
正如作者在书中所说:

重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。

这种情况可以使用JavaBean模式来改进,不过JavaBean模式会引进其他问题。JavaBean在不同时期状态不一致。

Builder模式

下面是使用Builder模式创建的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class ReceiverBuilder {
private final String name;
private final String address;
private final Integer age;
private final String gender;
private final String nickname;
private final String occupation;
private final Double salary;
public static class Builder {
private final String name;
private final String address;
private Integer age;
private String gender;
private String nickname;
private String occupation;
private Double salary;
public Builder(String name, String address) {
this.name = name;
this.address = address;
}
public Builder setAge(Integer age) {
this.age = age;
return this;
}
public Builder setGender(String gender) {
this.gender = gender;
return this;
}
public Builder setNickname(String nickname) {
this.nickname = nickname;
return this;
}
public Builder setOccupation(String occupation) {
this.occupation = occupation;
return this;
}
public Builder setSalary(Double salary) {
this.salary = salary;
return this;
}
public ReceiverBuilder builder() {
return new ReceiverBuilder(this);
}
}
private ReceiverBuilder(Builder builder) {
name = builder.name;
address = builder.address;
age = builder.age;
gender = builder.gender;
nickname = builder.nickname;
occupation = builder.occupation;
salary = builder.salary;
}
public void selfIntroduce() {
System.out.println(
"姓名:" + name + ",地址:" + address + ",年龄:" + age + ",性别:" + gender + ",称号:" + nickname + ",职业:" + occupation + ",工资:" + salary);
}
}

下面new一个对象,并调用selfIntroduce()方法:

1
2
ReceiverBuilder likuiRe = new ReceiverBuilder.Builder("李逵","梁山").setAge(25).setGender("m").setNickname("黑旋风").setOccupation("杀手").setSalary(10000.00).builder();
likuiRe.selfIntroduce();

控制台输出如下:

姓名:李逵,地址:梁山,年龄:25,性别:m,称号:黑旋风,职业:杀手,工资:10000.0

显然,这种模式具有良好的可读性,并且当参数增多时很方便扩展。

总结

Builder模式的主要使用场景是:

  • 当面对多个属性,且其中有些是可选属性时;
  • 对象属性会扩展时。

另外Builder模式的缺点也是很明显的,创建对象前要创建它的Builder,这样会增大系统开销。

最后,举个使用Builder模式创建对象的例子:

1
org.apache.http.client.config.RequestConfig


补充:为什么要把成员变量设置成final?
final修饰成员变量时,成员变量一旦初始化以后,不允许修改。从而保证成员变量的值不会被改变。