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,其中cpuram是必填参数,而其他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 类中,

  1. 先创建建造者,并且通过 set() 方法设置建造者的变量值,然后在使用 build() 方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。
  2. 除此之外,我们把 ResourcePoolConfig 的构造函数改为 private 私有权限。这样我们就只能通过建造者来创建 ResourcePoolConfig 类对象。
  3. 并且,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>maxIdleResourcePoolConfig config = new ResourcePoolConfig.Builder()        .setName("dbconnectionpool")        .setMaxTotal(16)        .setMaxIdle(10)        .setMinIdle(12)        .build();