建造者(Builder)模式指将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。它是把对象的构建和表述分离。
也就是通过多个简单对象组装出一个复杂对象的过程,比如机器人需要多步简单的组装、装修需要各个子步骤的合并完成:
建造者模式主要由产品(Product)、抽象建造者(Builder)、具体建造者(Concrete Builder)和指挥者(Director)四个角色构成,如下面的类图所示:
Product
:包含多个组件的产品,需要由具体建造者进行创建AbstractBuilder
:创建具体建造者的接口,通常还包括一个返回产品的接口Concrete Builder
:继承/实现抽象建造者,完成产品的各个部分的具体创建方法Director
:负责调用建造者以及内部具体产品对象的构建,在这之中不涉及具体的产品信息根据上面的类图我们可以写出如下代码:
//产品角色
public class Product {
public void setPartA(){}
public void setPartB(){}
public void setPartC(){}
public void show(){
System.out.println("完成产品构建一次");
}
}
//抽象建造者
public abstract class AbstractBuilder {
protected Product product = new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
public Product getResult() {
return product;
}
}
//具体建造者
public class Builder1 extends AbstractBuilder{
@Override
public void buildPartA() {
product.setPartA();
}
@Override
public void buildPartB() {
product.setPartB();
}
@Override
public void buildPartC() {
product.setPartC();
}
}
public class Builder2 extends AbstractBuilder{
@Override
public void buildPartA() {
product.setPartA();
}
@Override
public void buildPartB() {
product.setPartB();
}
@Override
public void buildPartC() {
product.setPartC();
}
}
//指挥者
public class Director {
private AbstractBuilder builder;
public Director(AbstractBuilder builder) {
this.builder = builder;
}
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
//客户端
public class Client {
public static void main(String[] args) {
Builder1 builder1 = new Builder1();
Builder2 builder2 = new Builder2();
Director director1 = new Director(builder1);
Director director2 = new Director(builder2);
Product product1 = director1.construct();
Product product2 = director2.construct();
product1.show();
product2.show();
}
}
说到应用场景,我们知道有不少创建对象的方式,比如使用构造函数、工厂模式都可以实现,那么他们之间的区别在哪呢?下面就来分别谈一下
构造函数是我们最常用的一种创建对象方式,对于简单的对象,只需要调用构造函数即可完成对象构建。但是对于复杂对象,举一个来自《设计模式之美》专栏的例子:
假设需要定义一个资源池配置类ResourcePoolConfig
,在这个资源池配置类中有name
、maxTotal
、maxIdle
、minIdle
四个成员变量(除name
外,其他变量都不是必填变量),请问如何用代码实现这个ResourcePoolConfig
类:
我们可以用if-else
来实现成员变量的配置:
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("maxTotal should be positive.");
}
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("minIdle should not be negative.");
}
this.minIdle = minIdle;
}
}
//...省略getter方法...
}
从这里可以发现,如果随着配置项的增多,构造函数内的参数会变得特别长。对于后续的代码管理会造成很大的负担,而且很有可能会造成参数传递错误。有没有其他改进方法?
因为除了name
成员变量,其他变量都是选填的,所以我们可以通过set
方法来设置这些变量,让调用者自主选择去选填:
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) {
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 be positive.");
}
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方法...
}
这样我们基本上解决了对实例对象的需求,但是如果有其他的比如这样的要求:
set
方法就无法完成配置项之间的依赖判断name
这样的必填配置项较多,又会出现成员参数列表过长的问题ResourcePoolConfig
类对象是不可变对象,我们就不能使用public
暴露set
方法对于上述的情况,就可以使用建造者模式来解决:
Builder
类中,先创建Builder
,通过set
方法设置Builder
的变量值,然后再使用build
方法真正创建对象前做集中的校验。最后校验成功再创建对象private
设置成私有权限,这样只能通过Builder
来创建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();
此外,建造者模式创建对象也能够避免对象存在无效状态。比如一个长方形类,必须同时具备长和宽两个属性才能是一个有效的长方形,而只有一个属性的话这个对象就没有任何意义。所以建造者模式中先设置建造者的变量然后再一次性地创建对象,能够保证对象一直出于有效状态。
java.lang.StringBuilder
和 java.lang.StringBuffer
这两个类中对字符串的操作使用了建造者模式,比如java.lang.StringBuilder
中的append
方法:
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
//通过调用AbstractStringBuilder中的append 方法来构造对象
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
此外,建造者模式的应用要根据业务不同来针对使用,比如创建的产品种类只有一种,那么就只需要一个具体建造者,这时也可以省略掉抽象建造者,甚至也可以省略掉指挥者的角色。
https://time.geekbang.org/column/article/199674
http://c.biancheng.net/view/1354.html
《Java 重学设计模式》