共计 3434 个字符,预计需要花费 9 分钟才能阅读完成。
Builder 模式,中文翻译为建造者模式或者构建者模式,也有人叫它生成器模式。
1. 定义
定义虽然基本没有。。用,因为大部分人都看不懂,但是还的说出来。。。
The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.
将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
2. 介绍
2.1 使用场景
当一个类的构造函数参数个数超过 4 个,而且这些参数有些是可选的参数,考虑使用构造者模式。
2.2 为了解决什么问题?
当一个类的构造函数参数超过 4 个,而且这些参数有些是可选的时,我们通常有两种办法来构建它的对象。例如我们现在有如下一个类计算机类 Computer
,其中cpu
与ram
是必填参数,而其他 3 个是可选参数,那么我们如何构造这个类的实例呢, 通常有两种常用的方式:
public class Computer {
private String cpu;// 必须
private String ram;// 必须
private int usbCount;// 可选
private String keyboard;// 可选
private String display;// 可选
}
第一:折叠构造函数模式(telescoping constructor pattern),这个我们经常用, 如下代码所示
public class Computer {
...
public Computer(String cpu, String ram) {this(cpu, ram, 0);
}
public Computer(String cpu, String ram, int usbCount) {this(cpu, ram, usbCount, "罗技键盘");
}
public Computer(String cpu, String ram, int usbCount, String keyboard) {this(cpu, ram, usbCount, keyboard, "三星显示器");
}
public Computer(String cpu, String ram, int usbCount, String keyboard, String display) {
this.cpu = cpu;
this.ram = ram;
this.usbCount = usbCount;
this.keyboard = keyboard;
this.display = display;
}
}
第二种:Javabean 模式,如下所示
public class Computer {
...
public String getCpu() {return cpu;}
public void setCpu(String cpu) {this.cpu = cpu;}
public String getRam() {return ram;}
public void setRam(String ram) {this.ram = ram;}
public int getUsbCount() {return usbCount;}
...
}
那么这两种方式有什么弊端呢?
第一种主要是使用及阅读不方便 。你可以想象一下,当你要调用一个类的构造函数时,你首先要决定使用哪一个,然后里面又是一堆参数,如果这些参数的类型很多又都一样,你还要搞清楚这些参数的含义,很容易就传混了。。。那酸爽谁用谁知道。
第二种方式在构建过程中对象的状态容易发生变化(因为暴露了 set 方法),造成错误。因为那个类中的属性是分步设置的,所以就容易出错。
为了解决这两个痛点,builder 模式就横空出世了。
2.3 Code
我们可以把校验逻辑放置到 Builder 类中,
- 先创建建造者,并且通过 set() 方法设置建造者的变量值,然后在使用 build() 方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。
- 除此之外,我们把 ResourcePoolConfig 的构造函数改为 private 私有权限。这样我们就只能通过建造者来创建 ResourcePoolConfig 类对象。
- 并且,ResourcePoolConfig 没有提供任何 set() 方法,这样我们创建出来的对象就是不可变对象了。
public class ResourcePoolConfig {
private String name;
private int maxTotal;
private int maxIdle;
private int minIdle;
private ResourcePoolConfig(Builder builder) {
this.name = builder.name;
this.maxTotal = builder.maxTotal;
this.maxIdle = builder.maxIdle;
this.minIdle = builder.minIdle;
}
//... 省略 getter 方法...
// 我们将 Builder 类设计成了 ResourcePoolConfig 的内部类。// 我们也可以将 Builder 类设计成独立的非内部类 ResourcePoolConfigBuilder。public static class Builder {
private static final int DEFAULT_MAX_TOTAL = 8;
private static final int DEFAULT_MAX_IDLE = 8;
private static final int DEFAULT_MIN_IDLE = 0;
private String name;
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public ResourcePoolConfig build() {
// 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
if (StringUtils.isBlank(name)) {throw new IllegalArgumentException("...");
}
if (maxIdle > maxTotal) {throw new IllegalArgumentException("...");
}
if (minIdle > maxTotal || minIdle > maxIdle) {throw new IllegalArgumentException("...");
}
return new ResourcePoolConfig(this);
}
public Builder setName(String name) {if (StringUtils.isBlank(name)) {throw new IllegalArgumentException("...");
}
this.name = name;
return this;
}
public Builder setMaxTotal(int maxTotal) {if (maxTotal <= 0) {throw new IllegalArgumentException("...");
}
this.maxTotal = maxTotal;
return this;
}
public Builder setMaxIdle(int maxIdle) {if (maxIdle < 0) {throw new IllegalArgumentException("...");
}
this.maxIdle = maxIdle;
return this;
}
public Builder setMinIdle(int minIdle) {if (minIdle < 0) {throw new IllegalArgumentException("...");
}
this.minIdle = minIdle;
return this;
}
}
}
// 这段代码会抛出 IllegalArgumentException,因为 minIdle>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
.setName("dbconnectionpool")
.setMaxTotal(16)
.setMaxIdle(10)
.setMinIdle(12)
.build();