建造者模式
什么是建造者模式
建造模式(Builder Pattern)是对象的创建模式。它可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。
简单理解的话就是不再使用简单的new关键字创建对象,然后使用setXxx的方式设置属性,而是将对象的创建和属性的设置相关联。
为什么要用建造者模式
当一个类的属性非常多,有的必填有的非必填,且有的属性之间有依赖关系。此时,单纯的使用new关键字和setXxx的话,不能优雅的拆创建对象,就比较适合使用建造者。举个例子:
我们需要定义连接池类 ConnectPool。属性值列表如下
属性名称 | 中文释义 | 是否必填 | 默认值 |
---|---|---|---|
name | 名称 | 是 | – |
maxConnect | 最大连接数 | 否 | 8 |
minConnect | 最小连接数 | 否 | 1 |
那么我们在创建这个ConnectPool类对象的时候,在使用建造者之前我们的构造函数是这样的:
public class ConnectPool{
private static final int DEFAULT_MAX = 8;
private static final int DEFAULT_MIN = 1;
private String name;
private int maxConnect= DEFAULT_MAX;
private int minConnect= DEFAULT_MIN;
public ConnectPool(String name, Integer maxConnect, Integer minConnect) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("name should not be empty.");
}
this.name = name;
if (maxConnect!= null) {
if (maxConnect<= 1) {
throw new IllegalArgumentException("maxConnect should be positive.");
}
this.maxConnect= maxConnect;
}
if (minConnect!= null) {
if (minConnect< 1) {
throw new IllegalArgumentException("maxConnect should not be negative.");
}
this.minConnect= minConnect;
}
}
//...省略getter方法...
}
现在只有3个属性,我们的构造函数就已经非常臃肿了,那么怎么解决构造函数臃肿的问题呢,你可能想到使用set属性值的方式,那么属性值的校验就必须放在构造函数外面,显然这是一个不可把控的风险(你不能确保后面使用这个类的同事在构造的时候进行校验)。
此时其实建造者模式就可以排上用场了,我们可以把校验逻辑放置到 Builder 类中,先创建建造者,并且通过 set() 方法设置建造者的变量值,然后在使用 build() 方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。除此之外,我们把 ConnectPool的构造函数改为 private 私有权限。这样我们就只能通过建造者来创建 ConnectPool类对象。并且,ConnectPool没有提供任何 set() 方法,这样我们创建出来的对象就是不可变对象了。
如何实现建造者模式
建造者模式一般都以Builder结尾,我们直接代码示例:
public class ConnectPool{
private String name;
private int maxConnect;
private int minConnect;
private ConnectPool(Builder builder) {
this.name = builder.name;
this.maxConnect= builder.maxConnect;
this.minConnect= builder.minConnect;
}
//...省略getter方法...
//我们将Builder类设计成了ConnectPool的内部类。
//我们也可以将Builder类设计成独立的非内部类ConnectPoolBuilder。
public static class Builder {
private static final int DEFAULT_MAX = 8;
private static final int DEFAULT_MIN = 1;
private String name;
private int maxConnect= DEFAULT_MAX;
private int minConnect= DEFAULT_MIN;
public ConnectPoolbuild() {
// 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("...");
}
if (minConnect> maxConnect) {
throw new IllegalArgumentException("...");
}
return new ConnectPool(this);
}
public Builder setName(String name) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("...");
}
this.name = name;
return this;
}
public Builder setMaxConnect(int maxConnect) {
if (maxConnect<= 1) {
throw new IllegalArgumentException("...");
}
this.maxConnect= maxConnect;
return this;
}
public Builder setMinConnect(int minConnect) {
if (minConnect< 1) {
throw new IllegalArgumentException("...");
}
this.minConnect= minConnect;
return this;
}
}
}
// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
ConnectPoolconfig = new ConnectPool.Builder()
.setName("dbconnectionpool")
.setMaxConnect(16)
.setMinConnect(10)
.build();
与工厂模式有何区别
- 工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
- 建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。
建造者模式有何缺陷
建造者模式来构建对象, 类中的成员变量,要在 Builder 类中重新再定义一遍,会使代码略显重复。
总结
何时使用建造者模式我们可以这样来判别:
- 1.需要生成的产品对象有复杂的内部结构。
- 2.需要生成的产品对象的属性相互依赖。
- 3.在对象创建过程中会使用到系统中的其他一些对象,这些对象在产品对象的创建过程中不易得到。