【建造者】设计模式:构建复杂对象的艺术

摘要

在软件开发中,我们经常会遇到需要创建复杂对象 的场景。这些复杂对象可能包含多个属性,并且这些属性的创建过程可能依赖于特定的顺序或者条件。

如果直接使用构造函数来创建这样的对象,可能会导致构造函数过于庞大和复杂,难以维护。这就是建造者设计模式大显身手的地方。

简介

建造者模式是一种创建型设计模式,它提供了一种创建复杂对象的最佳方式。它能够让你分步骤创建复杂对象,并允许你只通过必要的步骤来构建对象,从而使得代码更加清晰和灵活。

为什么需要建造者模式

在平时开发中,创建对象最常用的方式是使用new关键字调用构造函数 或者 set 函数来完成。那在什么场景下,这种方式就不适用了,需要使用建造者模式来创建对象呢?

假设对象的属性特别多,当使用构造函数创建对象时,则参数列表太长,影响代码的可读性和易用性

再换一个思路,如果通过构造函数设置必填项通过set方法设置可选项

此时,还是没有用到建造者模式,那如果还需要解决下面3个问题,现在的设计思路就无法满足了。

  • 如果必填项有很多,把这些必填项都放到构造函数中设置,那么构造函数又会出现参数列表很长的问题。如果把必填项通过set函数设置,那校验这些必填项是否已经填写的逻辑就无处安放了。
  • 如果参数之间有一定的依赖关系,按照现在的设计思路,这些参数之间的依赖校验逻辑无处安放。
  • 如果我们希望类对象是不可变对象,即在对象创建好后,就不能再修改对象内部属性值。那就不能在类中暴露set方法。

为了解决这些问题,建造者模式就派上用场了

比如下面这段代码:

我们把校验逻辑放到Builder类中,先创建建造者,并且通过set方法设置建造者的变量值,然后在使用buid()方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。

此外,我们把ResourcePoolConfig的构造函数设置为私有,这样就只能通过建造者来创建ResourcePoolConfig类对象,且ResourcePoolConfig没有提供任何set方法,这样创建出来的对象就是不可变对象了。

java 复制代码
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;
  }

  //我们将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之后,对象处于无效状态。

java 复制代码
Rectangle r = new Rectange(); // r is invalid
r.setWidth(2); // r is invalid
r.setHeight(3); // r is valid

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

总结

建造者设计模式通过将复杂对象的构建过程与表示分离,提供了一种清晰和灵活的方式来创建复杂对象。它特别适用于对象的创建过程复杂或者对象的创建过程需要根据不同的场景进行定制的情况。通过使用建造者模式,我们可以保持代码的清晰和可维护性,同时提供灵活的对象创建过程。

相关推荐
风象南30 分钟前
Spring Boot 实现文件秒传功能
java·spring boot·后端
橘猫云计算机设计31 分钟前
基于django优秀少儿图书推荐网(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·python·小程序·django·毕业设计
黑猫Teng35 分钟前
Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南
java·spring boot·后端
星河浪人41 分钟前
Spring Boot启动流程及源码实现深度解析
java·spring boot·后端
佩奇的技术笔记42 分钟前
中级:Maven面试题精讲
java·面试·maven
Lizhihao_1 小时前
JAVA-堆 和 堆排序
java·开发语言
极客先躯1 小时前
高级java每日一道面试题-2025年3月21日-微服务篇[Nacos篇]-什么是Nacos?
java·开发语言·微服务
工业互联网专业1 小时前
基于springboot+vue的动漫交流与推荐平台
java·vue.js·spring boot·毕业设计·源码·课程设计·动漫交流与推荐平台
雷渊1 小时前
深入分析Spring的事务隔离级别及实现原理
java·后端·面试
rebel1 小时前
Java获取excel附件并解析解决方案
java·后端