现在,ResourcePoolConfig 只有 4 个可配置项,对应到构造函数中,也只有 4 个参数,参数的个数不多。但是,如果可配置项逐渐增多,变成了 8 个、10 个,甚至更多,那继续沿用现在的设计思路,构造函数的参数列表会变得很长,代码在可读性和易用性上都会变差。例如:
// 参数太多,导致可读性差、参数可能传递错误 ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool", 16, null, 8, null, false , true, 10, 20,false, true);
我们可以通过set()
函数来给成员变量赋值,以替代冗长的构造函数。其中,配置项 name 是必填的,所以放到构造函数中设置,强制创建类对象的时候就要填写。其他配置项 maxTotal、maxIdle、minIdle
都不是必填的,所以我们通过 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() 方法设置可选配置项,就能实现我们的设计需求。
// ResourcePoolConfig使用举例 ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool"); config.setMaxTotal(16); config.setMaxIdle(8); //...省略其它的set方法...
1 建造者模式构建复杂对象【静态内部类形式】
如果我们把问题的难度再加大点,比如,还需要解决下面这三个问题,那现在的设计思路就不能满足了
- name 是必填的,所以,我们把它放到构造函数中,强制创建对象的时候就设置。如果必填的配置项有很多,把这些必填配置项都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填项也通过 set() 方法设置,那校验这些必填项是否已经填写的逻辑就无处安放了(因为set方法是调用者可选的)。
- 假设配置项之间有一定的依赖关系,比如,如果用户设置了 maxTotal、maxIdle、minIdle 其中一个,就必须显式地设置另外两个;或者配置项之间有一定的约束条件,比如,maxIdle 和 minIdle 要小于等于 maxTotal。如果我们继续使用现在的设计思路,那这些配置项之间的依赖关系或者约束条件的校验逻辑就无处安放了。
- 如果我们希望 ResourcePoolConfig 类对象是不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属性值。要实现这个功能,我们就不能在 ResourcePoolConfig 类中暴露 set() 方法。为了解决这些问题,建造者模式就派上用场了。
为了解决这些问题,建造者模式就派上用场了。
- 我们可以把校验逻辑放置到 Builder 类中,先创建建造者,并且通过 set() 方法设置建造者的变量值
- 在使用 build() 方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。
- 除此之外,我们把 ResourcePoolConfig 的构造函数改为 private 私有权限。这样我们就只能通过建造者来创建 ResourcePoolConfig 类对象。并且,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();
这里我们将Builder类设计成了ResourcePoolConfig的内部类。这也是我们常见的做法,其实LomBok中的@Builder
注解就是一种建造者模式。这也是我们最常见的建造者模式:包含一个复杂产品和一个具体建造者,且这个具体建造者是复杂产品的静态内部类,不需要抽象建造者和指挥者了
2 建造者模式构建复杂对象【独立建造者类模式】
当然还有拆分的形式:
复杂产品对象
public class ResourcePoolConfig { private String name; private int maxTotal; private int maxIdle; private int minIdle; protected ResourcePoolConfig(ResourcePoolConfigBuilder builder) { this.name = builder.name; this.maxTotal = builder.maxTotal; this.maxIdle = builder.maxIdle; this.minIdle = builder.minIdle; } }
具体建造者对象
class ResourcePoolConfigBuilder { private static final int DEFAULT_MAX_TOTAL = 8; private static final int DEFAULT_MAX_IDLE = 8; private static final int DEFAULT_MIN_IDLE = 0; protected String name; protected int maxTotal = DEFAULT_MAX_TOTAL; protected int maxIdle = DEFAULT_MAX_IDLE; protected int minIdle = DEFAULT_MIN_IDLE; public ResourcePoolConfig build() { // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等 if (Strings.isBlank(name)) { throw new IllegalArgumentException("..."); } if (maxIdle > maxTotal) { throw new IllegalArgumentException("maxIdle > maxTotal"); } if (minIdle > maxTotal || minIdle > maxIdle) { throw new IllegalArgumentException("minIdle > maxTotal || minIdle > maxIdle"); } return new ResourcePoolConfig(this); } public ResourcePoolConfigBuilder setName(String name) { if (Strings.isBlank(name)) { throw new IllegalArgumentException("..."); } this.name = name; return this; } public ResourcePoolConfigBuilder setMaxTotal(int maxTotal) { if (maxTotal <= 0) { throw new IllegalArgumentException("maxTotal <= 0"); } this.maxTotal = maxTotal; return this; } public ResourcePoolConfigBuilder setMaxIdle(int maxIdle) { if (maxIdle < 0) { throw new IllegalArgumentException("maxIdle < 0"); } this.maxIdle = maxIdle; return this; } public ResourcePoolConfigBuilder setMinIdle(int minIdle) { if (minIdle < 0) { throw new IllegalArgumentException("minIdle < 0"); } this.minIdle = minIdle; return this; } }
测试方法如下:
public static void main(String[] args) { ResourcePoolConfig resourcePoolConfig = new ResourcePoolConfigBuilder() .setName("dbconnectionpool") .setMaxTotal(16) .setMaxIdle(10) .setMinIdle(12) .build(); System.out.println(resourcePoolConfig); }
打印测试结果:
这里ResourcePoolConfigBuilder 自己充当了指挥者的角色,按照步骤执行方法组装构造校验好了ResourcePoolConfig对象。
模式对比
这里我们主要比较下建造者模式和工厂模式。
建造者模式与工厂模式
其实通篇学习下来感觉工厂模式和建造者模式很相似,都是将对象与创建过程分离,那么他们有什么区别呢?
- 建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
- 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
- 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,定制化地创建不同的对象。也就是工厂模式是用来创建几种相似类型的对象,关注一种父类(接口类)下不同子类的创建;而建造者模式则是创建一种类型的但内部数据有不同表现形式的对象,关注一种类型不同对象的创建过程。
总结一下
和工厂模式类似,建造者模式也致力于将对象的创建与使用分离。不同的是工厂模式的产品是具有抽象产品的,也就是具体产品可以有各自不同的方法实现,建造者模式的产品是固定的复杂对象,不同的具体建造者所能改变的也只是产品的组成部分属性值,产品的方法实现是固定的。但建造者模式拥有指挥者角色可以依据不同场景下的要求更好的调节产品内组成部分的依赖(顺序或种类)从而改变产品的特征。我感觉将二者结合起来应对复杂场景设计应该非常有帮助。