Skip to content

设计模式:创建型—建造者模式

Published: at 10:54:14

建造者模式(Builder Pattern)是一个比较常用的创建型设计模式,又叫Builder 模式,中文翻译为建造者模式或者构建者模式,也有人叫它生成器模式

定义

英文定义:

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.

翻译成中文的大概意思就是:

Builder 设计模式的目的是将复杂对象的构造与其表示分离。通过这样做,相同的构建过程可以创建不同的表示。

为什么需要建造者模式

首先,我们知道,建造者模式也是属于创建型的设计模式,所以它的目的也是为了创建一个类的,那什么时候需要我们用到建造者模式,而不是用我们经常使用的new(Java)方式来创建呢?

当使用new的方式创建的一个复杂对象时,初始化的参数我们一般都是通过构造函数来传递的,当参数过多、有必填参数校验、参数有依赖(比如参数A填了后参数B就必填,否则参数B不必填等)等多种复杂情况时,使用起来就不是那么优雅了。

参数过多,构造函数参数列表就会边长,代码的可读写和易用性就会变得很差,而且在使用的时候还很容易搞错参数的顺序,从而导致不容易发现的BUG。

为了解决上面的一系列问题,就引入了一个Builder类,我们把参数的必填等校验逻辑放入到Builder类中,我们创建类的时候先创建Builder(建造者)类,然后通过set方法设置建造者的变量,最后使用build()方法来做最后的集中校验,校验通过才会创建我们的目标对象。

明白了为什么要使用建造者模式后,下面看一个代码示例。

示例

下面是一个资源池配置类 ResourcePoolConfig,有4个入参。

  1. 使用new的方式构造对象:

    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方法...
    }

    参数过多时使用构造方法创建对象:

    // 参数太多,导致可读性差、参数可能传递错误
    ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool", 16, null, 8, null, false , true, 10, 20falsetrue);
  2. 使用Builder模式

    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();

与工厂模式的区别

网上有一个经典的例子很好地解释了两者的区别。

顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。

对于具体的披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。

实际上,我们也不要太学院派,非得把工厂模式、建造者模式分得那么清楚,我们需要知道的是,每个模式为什么这么设计,能解决什么问题。只有了解了这些最本质的东西,我们才能不生搬硬套,才能灵活应用,甚至可以混用各种模式创造出新的模式,来解决特定场景的问题。