建造者模式也称为构建者模式或者生成器模式。在看这篇文章之前可以思考思考两个问题:直接使用构造函数或者配合setter方法就能创建对象,为什么还需要使用建造者模式呢?构建者模式和工厂模式都可以创建对象,他们又会有什么区别呢?在看完这篇文章你想必就会有答案了。
在平时的开发中,创建一个对象的常用方式就是使用new关键字调用类的构造函数来完成,假设有这样一道代码设计方面的面试题:编写代码实现一个资源配置类ResourcePoolConfig,这里的资源配置池可以简单地被理解为线程池、连接池和对象池等。假设这个配置类中有name、maxTotal、maxIdle、minIdle等成员变量。其中name是必填选项,且没有默认值,其他的非必填有默认值。对于这种问题,有一些开发经验的很轻松就能实现这个资源配置类。比较容易想到的是下面这种实现方式;
public class ResourcePoolConfig{
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(String name , Integer maxTotal , Integer maxIdle , Integer minIdle){
if(StringUtils.isBlank(name))
throw new IllegalArgumentException("name should not be empty.");
this.name = name;
if(maxTotal != null){
if(maxTotal <= 0){
throw new IllegalArgumentException("maxIdle should not be negative.");
}
}
this.maxTotal = maxTotal;
if(maxIdle != null){
if(maxIdle <= 0){
throw new IllegalArgumentException("maxIdle should not be negative.");
}
}
this.maxIdle = maxIdle;
if(minIdle != null){
if(minIdle <= 0){
throw new IllegalArgumentException("maxIdle should not be negative.");
}
}
this.minIdle = minIdle;
}
//省去getter方法
}
对于当前这个例子只有4个配置项,对应到构造函数并不多。但是当配置项逐渐变多的时候,按照上述的设计思路,构造函数的列表就会变得很长,代码的可读性和易用性就会变得很差。在使用构造函数的时候也可能出错。
既然上面提到了,参数太多使用太麻烦,我们解决上面的方式也很简单,我们使用setter来替代构造函数为成员变量赋值。
public class ResourcePoolConfig{
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(String name , Integer maxTotal , Integer maxIdle , Integer minIdle){
if(StringUtils.isBlank(name))
throw new IllegalArgumentException("name should not be empty.");
this.name = name;
}
public void setMaxTotal(int maxTotal){
if(maxTotal <= 0)
throw new IllegalArgumentException("maxTotal should not be negative.");
this.maxTotal = maxTotal;
}
public void setMaxIdle(int maxIdle){
if(maxIdle <= 0)
throw new IllegalArgumentException("maxIdle should not be negative.");
this.maxIdle = maxIdle;
}
public void setMinIdle(int minIdle){
if(minIdle <= 0)
throw new IllegalArgumentException("minIdle should not be negative.");
this.minIdle = minIdle;
}
//省去getter方法
}
重构之后的代码可读性和易用性就提高了很多。
使用建造者模式做参数校验
从始至终我们都没用到建造者模式,只是使用了构造函数和setter方法设置可选项,即可满足需求。但是如果我们加大难度,就不一定了。比如还需要满足以下三个要求呢?
- name是必填项
- 如果设置了其他三个成员变量中的一个就要显示的设置其他的,又或者maxIdle,minIdle都要小于maxTotal
- 我们希望配置类对象是不可变对象,创建好了之后就不能修改内部的值
为了解决这几个问题那么建造者模式就登场了,我们利用建造者模式对代码进行重构。
public class ResourcePoolConfig{
private String name;
private int maxTotal;
private int maxIdle;
private int minIdle;
public ResourcePoolConfig(Builder builder){
this.name = builder.name;
this.maxTotal = builder.maxTotal;
this.minIdle = builder.minIdle;
this.maxIdle = builder.maxIdle;
}
//省去getter方法
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 || minIdle > maxTotal)
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 setMinIdle(int minIdle){
if(maxIdle <= 0)
throw new IllegalArgumentException("....");
this.minIdle = minIdle;
return this;
}
public Builder setMaxIdle(int MaxIdle){
if(maxIdle <= 0)
throw new IllegalArgumentException("....");
this.maxIdle = maxIdle;
return this;
}
}
}
这样设计代码就能满足上面的需求,并且能够避免对象处于一个无效状态。
建造者模式与工厂模式的区别
建造者模式和工厂模式都可以用来创建对象,二者的区别是啥呢?
工厂模式用来创建类型不同但相关的对象(继承同一父类或同一接口的子类),由给定的参数来决定创建哪种类型的对象;建造者模式用来创建同一类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同地对象。