设计模式-建造者模式

发布于 — 2019 年 10 月 14 日
#design

我们明明可以直接使用构造函数或者配合set方法就能创建对象, 为什么还需要通过建造者模式来创建呢.

建造者模式和工厂模式都可以创建对象, 他们之间的区别是什么?

为什么需要建造者模式

在某些配置类中, 有大量的参数需要传入, 并且这些参数有一些是必须的, 有一些是可选的.

某些参数之间有依赖关系, 比如当用户设置了A, 那么就必须设置B等等.

并且我们希望类对象是不可变对象, 也就是说对象在创建好之后, 就不能修改内部的属性值. 要实现这个功能, 我们就不能暴露set方法.

这时我们对这个类的创建虽然还可以通过构造函数来进行创建, 但是复杂度和可读性都不友好. 这时就可以使用建造者模式来进行对象的创建.

将校验逻辑放到Builder类中, 先创建建造者, 并通过set方法来设置建造者的变量值, 然后在build方法真正创建对象之前, 做集中的校验, 校验通过之后才会创建对象. 并且将类的构造函数设置成private, 这样就只能通过建造者来创建对象. 同时将不再提供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();

为了避免这种无效状态的存在, 我们就需要使用构造函数一次性初始化好所有的成员变量. 如果构造函数参数过多, 我们就需要考虑使用建造者模式, 先设置建造者的变量, 然后再一次性的创建对象, 让对象一直处于有效状态.

使用建造者模式创建对象, 还能避免对象存在无效状态. 假如我们定义了一个长方形, 如果不使用建造者模式, 而是使用set的方式, 那么在调用第一个set之后和调用第二个set之前, 这个对象是处于无效状态的.

与[[Blog-Posts/coding/design/工厂模式]]的区别

建造者模式创建的是同一种类型的复杂对象, 通过设置不同的可选参数, 来定制化的创建不同的对象

而工厂模式则是创建不同但是相关类型的对象(继承同一父类或者接口的一组子类), 通过给定的参数来决定创建哪种类型的对象.