建造者模式:详解构造函数、set方法、建造者模式三种对象创建方式
为什么需要建造者模式
一般情况下,我们可以通过构造函数来创建一个对象后初始化,也能通过set方法手动赋值。
我的一个思路是,如果参数过多,写在构造函数中,必须要保证正确的顺序,会比较麻烦。
看个例子:我们需要定义一个资源池配置类ResourcePoolConfig,这里的资源池可以理解为线程池、连接池、对象池等。他有几个成员变量:
普通情况下或者没学习构造者模式之前,这就是一个普通的类的实现:
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方法... }
但是如果参数的数量增加哦到8个、10个甚至更多,参数列表或很长, 可读性和易用性会变差,当参数顺序错误的时候,会导致隐蔽的bug。
针对这个情况,我们可以用setter复制,以代替冗长的构造函数。必须的参数我们放进构造函数,不是必须有的我们仿造setter中让调用者自行选择是否赋值。
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方法... }
那什么时候用到构造者模式呢?
- 如果必填的参数很多,都放进构造函数中又会出现参数列表过长的情况;
- 参数之间存在依赖关系。比如maxIdle和minIdle要小于maxTotal。(使用者调用setter的顺序无法保证,校验逻辑也就变得很难去实现);
- 希望ResourceConfig是不可变对象,创建好之后不能改变属性值。那么就不能暴露setter。
可以将校验逻辑放在Builder类中,先创建builder,通过setter设置变量值,然后使用build在真正创建对象之前进行集中校验;除此之外我们将ResourcePoolConfig的构造函数改为私有,这样就只能通过builder来创建ResourcePoolConfig对象;且ResourcePoolConfig没有setter方法,保证了不可变。
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();
使用构造者模式还能避免对象的无效状态(可以理解为参数不完整?):当普通的setter使用时:
Rectangle r = new Rectange(); // r is invalid r.setWidth(2); // r is invalid r.setHeight(3); // r is valid
使用构造者模式,可以在所有参数设置完成后再build不然是不允许build。
与工厂模式的区别
工厂模式:根据给定的参数决定创建哪种对象。
构造者模式:实现了多参数情况下(且有必填项和非必填项),同一类型对象的参数的校验和对象的创建。