• 设计模式学习笔记(五)建造者模式和其他对象创建方式的区别


    一、建造者模式介绍

    1.1 建造者模式的定义

    建造者(Builder)模式指将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。它是把对象的构建和表述分离。

    也就是通过多个简单对象组装出一个复杂对象的过程,比如机器人需要多步简单的组装、装修需要各个子步骤的合并完成:

    1.2 建造者模式的特点

    1. 优点

      • 建造者模式将零件细节进行了封装,实现了构建和表示分离

      • 建造者可以对创建过程逐步细化,而不对其它模块产生影响,能方便控制细节风险

    2. 缺点

      • 如果产品内部变化,建造者也有整体进行修改,后期维护成本较大

    二、建造者模式结构与实现

    2.1 建造者模式的结构

    建造者模式主要由产品(Product)、抽象建造者(Builder)、具体建造者(Concrete Builder)和指挥者(Director)四个角色构成,如下面的类图所示:

    1. Product:包含多个组件的产品,需要由具体建造者进行创建
    2. AbstractBuilder:创建具体建造者的接口,通常还包括一个返回产品的接口
    3. Concrete Builder:继承/实现抽象建造者,完成产品的各个部分的具体创建方法
    4. Director:负责调用建造者以及内部具体产品对象的构建,在这之中不涉及具体的产品信息

    2.2 建造者模式的实现

    根据上面的类图我们可以写出如下代码:

    //产品角色
    public class Product {
    
        public void setPartA(){}
    
        public void setPartB(){}
    
        public void setPartC(){}
    
        public void show(){
            System.out.println("完成产品构建一次");
        }
    }
    
    //抽象建造者
    public abstract class AbstractBuilder {
    
        protected Product product = new Product();
    
        public abstract void buildPartA();
    
        public abstract void buildPartB();
    
        public abstract void buildPartC();
    
        public Product getResult() {
            return product;
        }
    }
    
    //具体建造者
    public class Builder1 extends AbstractBuilder{
    
        @Override
        public void buildPartA() {
            product.setPartA();
        }
    
        @Override
        public void buildPartB() {
            product.setPartB();
        }
    
        @Override
        public void buildPartC() {
            product.setPartC();
        }
    }
    public class Builder2 extends AbstractBuilder{
    
        @Override
        public void buildPartA() {
            product.setPartA();
        }
    
        @Override
        public void buildPartB() {
            product.setPartB();
        }
    
        @Override
        public void buildPartC() {
            product.setPartC();
        }
    }
    
    //指挥者
    public class Director {
    
        private AbstractBuilder builder;
    
        public Director(AbstractBuilder builder) {
            this.builder = builder;
        }
    
        public Product construct() {
            builder.buildPartA();
            builder.buildPartB();
            builder.buildPartC();
            return builder.getResult();
        }
    }
    
    //客户端
    public class Client {
        public static void main(String[] args) {
            Builder1 builder1 = new Builder1();
            Builder2 builder2 = new Builder2();
            Director director1 = new Director(builder1);
            Director director2 = new Director(builder2);
            Product product1 = director1.construct();
            Product product2 = director2.construct();
            product1.show();
            product2.show();
        }
    }
    

    三、建造者模式和其他对象创建方式的区别

    说到应用场景,我们知道有不少创建对象的方式,比如使用构造函数、工厂模式都可以实现,那么他们之间的区别在哪呢?下面就来分别谈一下

    3.1 构造函数创建对象

    构造函数是我们最常用的一种创建对象方式,对于简单的对象,只需要调用构造函数即可完成对象构建。但是对于复杂对象,举一个来自《设计模式之美》专栏的例子:

    假设需要定义一个资源池配置类ResourcePoolConfig,在这个资源池配置类中有namemaxTotalmaxIdleminIdle四个成员变量(除name外,其他变量都不是必填变量),请问如何用代码实现这个ResourcePoolConfig类:

    3.1.1 逻辑判断成员变量

    我们可以用if-else来实现成员变量的配置:

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

    从这里可以发现,如果随着配置项的增多,构造函数内的参数会变得特别长。对于后续的代码管理会造成很大的负担,而且很有可能会造成参数传递错误。有没有其他改进方法?

    3.1.2 set方法设置成员变量

    因为除了name成员变量,其他变量都是选填的,所以我们可以通过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方法就无法完成配置项之间的依赖判断
    • 如果像name这样的必填配置项较多,又会出现成员参数列表过长的问题
    • 如果希望ResourcePoolConfig类对象是不可变对象,我们就不能使用public 暴露set方法

    对于上述的情况,就可以使用建造者模式来解决:

    • 将校验逻辑放在 Builder类中,先创建Builder,通过set方法设置Builder的变量值,然后再使用build方法真正创建对象前做集中的校验。最后校验成功再创建对象
    • 将构造方法添加 private设置成私有权限,这样只能通过Builder来创建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();
    

    此外,建造者模式创建对象也能够避免对象存在无效状态。比如一个长方形类,必须同时具备长和宽两个属性才能是一个有效的长方形,而只有一个属性的话这个对象就没有任何意义。所以建造者模式中先设置建造者的变量然后再一次性地创建对象,能够保证对象一直出于有效状态。

    3.2 与工厂模式的区别

    1. 工厂模式:用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
    2. 创建者模式:用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象

    四、建造者模式的应用场景

    4.1 JDK源码

    4.1.1 java.lang.StringBuilder java.lang.StringBuffer

    这两个类中对字符串的操作使用了建造者模式,比如java.lang.StringBuilder中的append方法:

    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    //通过调用AbstractStringBuilder中的append 方法来构造对象
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    

    此外,建造者模式的应用要根据业务不同来针对使用,比如创建的产品种类只有一种,那么就只需要一个具体建造者,这时也可以省略掉抽象建造者,甚至也可以省略掉指挥者的角色。

    参考资料

    https://time.geekbang.org/column/article/199674

    http://c.biancheng.net/view/1354.html

    《Java 重学设计模式》

  • 相关阅读:
    4章假设检验
    参数估计
    3 抽样分布
    2.描述性统计的matlab 实现
    《做时间的朋友》第五章 小心所谓成功学
    《把时间当作朋友》第四章 开拓我们的心智
    《把时间当作朋友》第三章 提高心智,和时间做朋友
    《把时间当作朋友》第二章 开启自己的心智
    《把时间当作朋友》第1章 心智的力量
    《把时间当作朋友》1
  • 原文地址:https://www.cnblogs.com/EthanWong/p/16061499.html
Copyright © 2020-2023  润新知