建造者模式同样是创建型设计模式的一种,用于解决复杂对象的创建问题。
为什么需要建造者模式
为了得到一个对象,通常情况下我们调用构造方法然后再配合 set 方法做一些初始化工作即可。假定我们现在有一个传感器,传感器的名称和类型不能为空、客户ID可选、如果客户ID不为空时安装时间也不能为空,反应到类的代码如下。
public class Sensor {
private String name;
private String type;
private Integer customerId;
private Date installTime;
public Sensor(String name, String type) {
this(name, type, null);
}
public Sensor(String name, String type, Integer customerId) {
this(name, type, customerId, null);
}
public Sensor(String name, String type, Integer customerId, Date installTime) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("名称不能为空");
}
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("类型不能为空");
}
if (customerId != null) {
if (installTime != null && installTime.compareTo(new Date()) > 0) {
throw new IllegalArgumentException("安装时间有误");
}
}
this.name = name;
this.type = type;
this.customerId = customerId;
this.installTime = installTime;
}
}
为了满足传感器各属性的限制,我们定义了三个构造方法,为了复用代码,我们的实现中参数少的构造方法会调用参数多的构造方法。当前只有三个构造方法还可以接受,如果以后需要再次扩展,如温度传感器需要配置报警的最低温度和最高温度,我们还需要为构造方法增加新的参数,具体如下。
public class Sensor {
private String name;
private String type;
private Integer customerId;
private Date installTime;
private Double lower;
private Double upper;
public Sensor(String name, String type, Integer customerId, Date installTime, Double lower, Double upper) {
... 省略部分代码
if ((lower != null && upper == null) || (upper != null && lower == null)) {
throw new IllegalArgumentException("报警温度有误");
}
if (lower != null && upper != null && lower > upper) {
throw new IllegalArgumentException("报警温度有误");
}
this.name = name;
this.type = type;
this.customerId = customerId;
this.installTime = installTime;
this.lower = lower;
this.upper = upper;
}
}
发现问题了吗?类的构造方法参数不断的增加,最终导致可读性极差,很容易传入错误的参数,使用者必须阅读相关注释或代码才能确实使用哪个构造方法。
那怎么解决这个问题呢?按照通常的思路,调用构造方法后再调 set 方法,我们只把传感器必须的属性作为构造方法参数,其他的属性通过 set 方法设置即可。代码如下。
Sensor sensor = new Sensor("温度传感器", "温度");
sensor.setInstallTime(new Date());
sensor.setCustomerId(1);
但是问题又来了,我们是需要进行参数校验的,对于上述的场景设置了客户ID后则必须设置安装时间,我们无法保证设置安装时间方法、设置客户ID方法的调用顺序,也就是说对于有依赖关系的属性现在我们无法进行参数校验。怎么办呢?为了保证构造方法参数不断扩展又需要进行参数校验,建造者模式就出现了。
如何实现一个建造者模式
使用建造者模式解决上述问题,代码如下。
public class Sensor {
private String name;
private String type;
private Integer customerId;
private Date installTime;
private Double lower;
private Double upper;
private Sensor(Build build) {
this.name = build.name;
this.type = build.type;
this.customerId = build.customerId;
this.installTime = build.installTime;
this.lower = build.lower;
this.upper = build.upper;
}
public static class Build {
private String name;
private String type;
private Integer customerId;
private Date installTime;
private Double lower;
private Double upper;
public String getName() {
return name;
}
public Build setName(String name) {
this.name = name;
return this;
}
public String getType() {
return type;
}
public Build setType(String type) {
this.type = type;
return this;
}
public Integer getCustomerId() {
return customerId;
}
public Build setCustomerId(Integer customerId) {
this.customerId = customerId;
return this;
}
public Date getInstallTime() {
return installTime;
}
public Build setInstallTime(Date installTime) {
this.installTime = installTime;
return this;
}
public Double getLower() {
return lower;
}
public Build setLower(Double lower) {
this.lower = lower;
return this;
}
public Double getUpper() {
return upper;
}
public Build setUpper(Double upper) {
this.upper = upper;
return this;
}
public Sensor build() {
if (this.name == null || this.name.isEmpty()) {
throw new IllegalArgumentException("名称不能为空");
}
if (this.type == null || this.type.isEmpty()) {
throw new IllegalArgumentException("类型不能为空");
}
if (this.customerId != null) {
if (this.installTime != null && this.installTime.compareTo(new Date()) > 0) {
throw new IllegalArgumentException("安装时间有误");
}
}
if ((this.lower != null && this.upper == null) || (this.upper != null && this.lower == null)) {
throw new IllegalArgumentException("报警温度有误");
}
if (this.lower != null && this.upper != null && this.lower > this.upper) {
throw new IllegalArgumentException("报警温度有误");
}
Sensor sensor = new Sensor(this);
return sensor;
}
}
}
使用方此时创建传感器类实例的代码如下。
Sensor sensor = new Sensor.Build().setName("温度传感器").setType("温度").setCustomerId(1).setInstallTime(new Date()).build();
我们将设置对象属性的代码转移到了建造者类中,然后在建造者类的 build 方法中完成了参数的校验。
总结
建造者模式用于解决复杂对象创建的问题,针对构造方法参数过长和具有依赖关系的参数校验问题,将参数设置的代码转移到建造者类,并通过建造者类的 build
方法完成存在依赖关系的参数校验。实现一个建造者模式通常的步骤如下。
- 在实体类中创建一个静态内部类 XxxBuilder,属性和实体类保持一致。
- 实体类提供一个私有的仅有一个类型为静态内部类的参数。
- 静态内部类的 setter 方法返回静态内部类实例,便于链式调用。
- 静态内部类提供 build 方法,用于参数校验及实例化实体类。