设计模式4-建造者模式

2次阅读

共计 3434 个字符,预计需要花费 9 分钟才能阅读完成。

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>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
        .setName("dbconnectionpool")
        .setMaxTotal(16)
        .setMaxIdle(10)
        .setMinIdle(12)
        .build();
正文完
 0