概述
建造者模式是一种创建型设计模式,主要目的在于将一个复杂的对象的创建分解为多个简单的步骤,然后逐步构建。
下面以一个资源池配置类ResourcePollConfig来进行说明。这个资源池配置类中有以下几个成员变量:
成员变量 | 解释 | 是否必填 | 默认值 |
---|---|---|---|
name | 资源名称 | 是 | 没有 |
maxTotal | 最大总资源数量 | 否 | 8 |
maxIdle | 最大空闲资源数量 | 否 | 8 |
minIdle | 最小空闲资源数量 | 否 | 0 |
使用构造函数
maxTotal、maxIdle、minIdle并不是必填项,所以创建对象的时候,参数传递null表示默认值
public class ResourcePoolConfig {
//默认值
private static final int DEFAULT_MAX_TOTLE = 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_TOTLE;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public ResourcePoolConfig(String name,Integer maxTotal,Integer maxIdle,Integer minIdle){
if(name == null || name == ""){
throw new IllegalArgumentException("name不能为空");
}
this.name = name;
if(maxTotal != null){
if(maxTotal <= 0){
throw new IllegalArgumentException("maxTotal应该为正");
}
this.maxTotal = maxTotal;
}
if(maxIdle != null){
if(maxTotal < 0){
throw new IllegalArgumentException("maxIdle不能为负数");
}
this.maxIdle = maxIdle;
}
if(minIdle != null){
if(minIdle < 0){
throw new IllegalArgumentException("minIdle不能为负数");
}
this.minIdle = minIdle;
}
}
}
目前只有4个可配置项,还比较能够接受,如果配置项变得更多会存在问题,
存在的问题在于:
- 参数过多导致代码可读性变差
- 使用构造函数的时候可能搞错参数顺序,导致参数可能传递错误
使用set()函数
可以使用set()函数赋值成员变量,构造函数只保留必填项来解决这个问题
public class ResourcePoolConfig {
private static final int DEFAULT_MAX_TOTLE = 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_TOTLE;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public ResourcePoolConfig(String name){
if(name == null || name == ""){
throw new IllegalArgumentException("name不能为空");
}
this.name = name;
}
public void setMaxTotal(int maxTotal) {
if(maxTotal <= 0){
throw new IllegalArgumentException("maxTotal应该为正");
}
this.maxTotal = maxTotal;
}
public void setMaxIdle(int maxIdle) {
if(maxTotal < 0){
throw new IllegalArgumentException("maxIdle不能为负数");
}
this.maxIdle = maxIdle;
}
public void setMinIdle(int minIdle) {
if(minIdle < 0){
throw new IllegalArgumentException("minIdle不能为负数");
}
this.minIdle = minIdle;
}
}
使用用例如下:
ResourcePoolConfig config = new ResourcePoolConfig("config");
config.setMaxTotal(16);
config.setMaxIdle(8);
使用起来代码在可读性和易用性上提高了。但仍存在问题
- 如果必填项有很多,那么就面临上述使用构造函数一样的问题,set()方法并没有完全解决此问题,需要说明的是,必填项是不能通过set()方法设置的,如果通过set()方法设置就不能校验必填项是否被给出。
- 如果配置项之间存在约束条件,比如此例中maxIdle要小于等于maxTotal,minIdle要小于等于maxIdle。这时使用set()方法设置变量,校验的逻辑就无处安放。同样的,如果存在依赖关系也是如此
- 如果希望配置类对象是不可变的,创建之后就不能修改内部属性值。那么就不应该暴露set()方法。
建造者模式
针对上述问题,就可以使用建造者模式。
将校验逻辑放到Builder类中,先创建建造者,并通过set()方法设置建造者的变量值,然后在使用build()方法真正创建对象之前,做集中的校验,校验通过后才会创建对象。除此之外可以将ResourcePoolConfig的构造函数设置为私有。这样就只能通过建造者创建对象。
public class ResourcePoolConfig {
private String name;
private int maxTotal;
private int maxIdle;
private int minIdle;
//私有化构造函数,只能通过Builder创建对象
private ResourcePoolConfig(Builder builder){
this.name = builder.name;
this.maxTotal = builder.maxTotal;
this.maxIdle = builder.maxIdle;
this.minIdle = builder.minIdle;
}
//也可以将Builder设计成独立的非内部类
public static class Builder{
private static final int DEFAULT_MAX_TOTLE = 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_TOTLE;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public ResourcePoolConfig build(){
//属性之间的关系可以放在这里集中校验
if(name == null || name == ""){
throw new IllegalArgumentException("name不能为空");
}
if(maxIdle > maxTotal){
throw new IllegalArgumentException("maxIdle不能大于maxTotal");
}
if(minIdle > maxIdle){
throw new IllegalArgumentException("minIdle不能大于maxIdle");
}
return new ResourcePoolConfig(this);
}
public Builder setName(String name) {
if (name == null || name == "") {
throw new IllegalArgumentException("name不能为空");
}
this.name = name;
return this;
}
public Builder setMaxTotal(int maxTotal) {
if (maxTotal <= 0) {
throw new IllegalArgumentException("maxTotal应该为正");
}
this.maxTotal = maxTotal;
return this;
}
public Builder setMaxIdle(int maxIdle) {
if (maxIdle < 0) {
throw new IllegalArgumentException("maxIdle不能为负数");
}
this.maxIdle = maxIdle;
return this;
}
public Builder setMinIdle(int minIdle) {
if (minIdle < 0) {
throw new IllegalArgumentException("minIdle不能为负数");
}
this.minIdle = minIdle;
return this;
}
}
}
建造者模式里面使用了链式调用的技巧,设置变量时返回自身便于用户使用
使用用例如下:
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
.setName("")
.setMaxTotal(16)
.setMaxIdle(10)
.setMinIdle(12)
.build();
可以看到简洁了不少,同时兼顾了校验逻辑。但缺点是代码有点重复,ResourcePoolConfig中的成员变量要在Builder中重新定义一遍。
实际上如果并不是很关心对象是否可变,在业务上也没有特别的校验逻辑,比如数据库映射的entity对象,可以直接暴露set()方法来设置值。
与工厂模式区别
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种对象。
建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”创建不同属性的对象。
建造者模式使用场景总结
- 如果必填属性很多,为了代码的可读性和易用性,使用建造者模式
- 如果类的属性之间存在一定的依赖关系或者约束条件,为了能够对依赖关系和约束条件进行集中校验,可以使用建造者模式
- 如果希望创建不可变对象,也就不能暴露set()方法。可以使用建造者模式,然后私有化构造函数。