Java设计模式中的建造者模式详解

 更新时间:2023年12月05日 09:20:01   作者:遇见更好的自己、  
这篇文章主要介绍了Java设计模式中的建造者模式详解,建造者模式使我们日常工作中比较常见的一种设计模式,和工厂模式一样属于创建型设计模式,用于解耦对象创建和对象使用的逻辑,需要的朋友可以参考下

前言

建造者模式使我们日常工作中比较常见的一种设计模式,和工厂模式一样属于创建型设计模式,用于解耦对象创建和对象使用的逻辑。

建造者设计模式和对象的构造函数或者set方法比较类似,那既然用构造函数或者set方法能创建对象,那我们为什么还需要建造者设计模式勒?

建造者和是工厂模式都是建造型的设计模式,那这两者的区别是什么勒?建造者的应用场景是什么勒?

为什么需要建造者模式

假设我们有如下的设计要求,我们需要定义一个资源池配置类 ResourcePoolConfig。

这里的资源池,你可以简单理解为线程池、连接池、对象池等。

在这个资源池配置类中,有以下几个成员变量,(name、maxTotal、maxIdle、minIdle)也就是可配置项。

现在,请你编写代码实现这个 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方法...
}

现在,ResourcePoolConfig 只有 4 个可配置项,对应到构造函数中,也只有 4 个参数,参数的个数不多。

但是,如果可配置项逐渐增多,变成了 8 个、10 个,甚至更多,那继续沿用现在的设计思路,构造函数的参数列表会变得很长,代码在可读性和易用性上都会变差。

在使用构造函数的时候,我们就容易搞错各参数的顺序,传递进错误的参数值,导致非常隐蔽的 bug。

用set方法的方式去创建这个类的话,代码示例如下:

// ResourcePoolConfig使用举例
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool");
config.setMaxTotal(16);
config.setMaxIdle(8);

如果我们把问题的难度再加大点,比如,还需要解决下面这三个问题,那现在的设计思路就不能满足了。

我们刚刚讲到,name 是必填的,所以,我们把它放到构造函数中,强制创建对象的时候就设置。如果必填的配置项有很多,把这些必填配置项都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填项也通过 set() 方法设置,那校验这些必填项是否已经填写的逻辑就无处安放了。

除此之外,假设配置项之间有一定的依赖关系,比如,如果用户设置了 maxTotal、maxIdle、minIdle 其中一个,就必须显式地设置另外两个;或者配置项之间有一定的约束条件,比如,maxIdle 和 minIdle 要小于等于 maxTotal。如果我们继续使用现在的设计思路,那这些配置项之间的依赖关系或者约束条件的校验逻辑就无处安放了。

如果我们希望 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();

实际上,如果我们并不是很关心对象是否有短暂的无效状态,也不是太在意对象是否是可变的。比如,对象只是用来映射数据库读出来的数据,那我们直接暴露 set() 方法来设置类的成员变量值是完全没问题的。而且,使用建造者模式来构建对象,代码实际上是有点重复的,ResourcePoolConfig 类中的成员变量,要在 Builder 类中重新再定义一遍。

与工程模式的区别?

在前言中,我们有提到建造者模式与工厂模式都是创建型的设计模式,那他们之间的区别是什么?

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

总结

如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合 set() 方法来解决。

但是,如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。

我们把类的必填属性放到构造函数中,强制创建对象的时候就设置。

如果必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。

如果我们把必填属性通过 set() 方法设置,那校验这些必填属性是否已经填写的逻辑就无处安放了。

如果类的属性之间有一定的依赖关系或者约束条件,我们继续使用构造函数配合 set() 方法的设计思路,那这些依赖关系或约束条件的校验逻辑就无处安放了。

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

构造函数配合 set() 方法来设置属性值的方式就不适用了。

除此之外,在今天的讲解中,我们还对比了工厂模式和建造者模式的区别。

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

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

到此这篇关于Java设计模式中的建造者模式详解的文章就介绍到这了,更多相关Java建造者模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++/java 继承类的多态详解及实例代码

    C++/java 继承类的多态详解及实例代码

    这篇文章主要介绍了C++/java 继承类的多态详解及实例代码的相关资料,需要的朋友可以参考下
    2017-02-02
  • Spring Boot项目打包指定包名实现示例

    Spring Boot项目打包指定包名实现示例

    这篇文章主要为大家介绍了Spring Boot项目打包指定包名实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • Java之Spring Bean 作用域和生命周期

    Java之Spring Bean 作用域和生命周期

    这篇文章主要介绍了Java Bean的作用域和生命周期,Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式,所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期,感兴趣的同学可以参考阅读
    2023-04-04
  • Spring中基于Java的配置@Configuration和@Bean用法详解

    Spring中基于Java的配置@Configuration和@Bean用法详解

    这篇文章主要介绍了Spring中基于Java的配置@Configuration和@Bean用法详解,Spring中为了减少xml中配置,可以声明一个配置类(例如SpringConfig)来对bean进行配置。,需要的朋友可以参考下
    2019-06-06
  • 一小时迅速入门Mybatis之Prepared Statement与符号的使用

    一小时迅速入门Mybatis之Prepared Statement与符号的使用

    这篇文章主要介绍了一小时迅速入门Mybatis之Prepared Statement与符号的使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • Java 数据结构进阶二叉树题集下

    Java 数据结构进阶二叉树题集下

    二叉树可以简单理解为对于一个节点来说,最多拥有一个上级节点,同时最多具备左右两个下级节点的数据结构。本文将带你通过实际题目来熟练掌握
    2022-04-04
  • springboot 中整合mybatis多数据源不使用JPA

    springboot 中整合mybatis多数据源不使用JPA

    这篇文章主要介绍了springboot 中整合mybatis多数据源不使用JPA,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java遍历输出指定目录、树形结构所有文件包括子目录下的文件

    Java遍历输出指定目录、树形结构所有文件包括子目录下的文件

    这篇文章主要介绍了Java遍历输出指定目录、树形结构下的所有文件包括子目录中的文件,需要的朋友可以参考下
    2015-07-07
  • Java正则表达式之Pattern类实例详解

    Java正则表达式之Pattern类实例详解

    Pattern类的作用在于编译正则表达式后创建一个匹配模式,下面这篇文章主要给大家介绍了关于Java正则表达式之Pattern类的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-01-01
  • 解决JavaEE开发中字符编码出现乱码的问题

    解决JavaEE开发中字符编码出现乱码的问题

    下面小编就为大家带来一篇解决JavaEE开发中字符编码出现乱码的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07

最新评论