【Java设计模式 设计模式与范式】创建型模式 五:建造者模式(下)

简介: 【Java设计模式 设计模式与范式】创建型模式 五:建造者模式(下)

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

// 参数太多,导致可读性差、参数可能传递错误
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool", 16, null, 8, null, false , true, 10, 20,false, true);

我们可以通过set() 函数来给成员变量赋值,以替代冗长的构造函数。其中,配置项 name 是必填的,所以放到构造函数中设置,强制创建类对象的时候就要填写。其他配置项 maxTotal、maxIdle、minIdle 都不是必填的,所以我们通过 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() 方法设置可选配置项,就能实现我们的设计需求。

// ResourcePoolConfig使用举例
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool");
config.setMaxTotal(16);
config.setMaxIdle(8);
//...省略其它的set方法...

1 建造者模式构建复杂对象【静态内部类形式】

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

  1. name 是必填的,所以,我们把它放到构造函数中,强制创建对象的时候就设置。如果必填的配置项有很多,把这些必填配置项都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填项也通过 set() 方法设置,那校验这些必填项是否已经填写的逻辑就无处安放了(因为set方法是调用者可选的)。
  2. 假设配置项之间有一定的依赖关系,比如,如果用户设置了 maxTotal、maxIdle、minIdle 其中一个,就必须显式地设置另外两个;或者配置项之间有一定的约束条件,比如,maxIdle 和 minIdle 要小于等于 maxTotal。如果我们继续使用现在的设计思路,那这些配置项之间的依赖关系或者约束条件的校验逻辑就无处安放了
  3. 如果我们希望 ResourcePoolConfig 类对象是不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属性值。要实现这个功能,我们就不能在 ResourcePoolConfig 类中暴露 set() 方法。为了解决这些问题,建造者模式就派上用场了。

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

  1. 我们可以把校验逻辑放置到 Builder 类中,先创建建造者,并且通过 set() 方法设置建造者的变量值
  2. 在使用 build() 方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。
  3. 除此之外,我们把 ResourcePoolConfig 的构造函数改为 private 私有权限。这样我们就只能通过建造者来创建 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();

这里我们将Builder类设计成了ResourcePoolConfig的内部类。这也是我们常见的做法,其实LomBok中的@Builder注解就是一种建造者模式。这也是我们最常见的建造者模式:包含一个复杂产品和一个具体建造者,且这个具体建造者是复杂产品的静态内部类,不需要抽象建造者和指挥者了

2 建造者模式构建复杂对象【独立建造者类模式】

当然还有拆分的形式:

复杂产品对象

public class ResourcePoolConfig {
    private String name;
    private int maxTotal;
    private int maxIdle;
    private int minIdle;
    protected ResourcePoolConfig(ResourcePoolConfigBuilder builder) {
        this.name = builder.name;
        this.maxTotal = builder.maxTotal;
        this.maxIdle = builder.maxIdle;
        this.minIdle = builder.minIdle;
    }
}

具体建造者对象

class ResourcePoolConfigBuilder {
    private static final int DEFAULT_MAX_TOTAL = 8;
    private static final int DEFAULT_MAX_IDLE = 8;
    private static final int DEFAULT_MIN_IDLE = 0;
    protected String name;
    protected int maxTotal = DEFAULT_MAX_TOTAL;
    protected int maxIdle = DEFAULT_MAX_IDLE;
    protected int minIdle = DEFAULT_MIN_IDLE;
    public ResourcePoolConfig build() {
        // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
        if (Strings.isBlank(name)) {
            throw new IllegalArgumentException("...");
        }
        if (maxIdle > maxTotal) {
            throw new IllegalArgumentException("maxIdle > maxTotal");
        }
        if (minIdle > maxTotal || minIdle > maxIdle) {
            throw new IllegalArgumentException("minIdle > maxTotal || minIdle > maxIdle");
        }
        return new ResourcePoolConfig(this);
    }
    public ResourcePoolConfigBuilder setName(String name) {
        if (Strings.isBlank(name)) {
            throw new IllegalArgumentException("...");
        }
        this.name = name;
        return this;
    }
    public ResourcePoolConfigBuilder setMaxTotal(int maxTotal) {
        if (maxTotal <= 0) {
            throw new IllegalArgumentException("maxTotal <= 0");
        }
        this.maxTotal = maxTotal;
        return this;
    }
    public ResourcePoolConfigBuilder setMaxIdle(int maxIdle) {
        if (maxIdle < 0) {
            throw new IllegalArgumentException("maxIdle < 0");
        }
        this.maxIdle = maxIdle;
        return this;
    }
    public ResourcePoolConfigBuilder setMinIdle(int minIdle) {
        if (minIdle < 0) {
            throw new IllegalArgumentException("minIdle < 0");
        }
        this.minIdle = minIdle;
        return this;
    }
}

测试方法如下:

public static void main(String[] args) {
        ResourcePoolConfig resourcePoolConfig = new ResourcePoolConfigBuilder()
                .setName("dbconnectionpool")
                .setMaxTotal(16)
                .setMaxIdle(10)
                .setMinIdle(12)
                .build();
        System.out.println(resourcePoolConfig);
    }

打印测试结果:

这里ResourcePoolConfigBuilder 自己充当了指挥者的角色,按照步骤执行方法组装构造校验好了ResourcePoolConfig对象。

模式对比

这里我们主要比较下建造者模式和工厂模式。

建造者模式与工厂模式

其实通篇学习下来感觉工厂模式和建造者模式很相似,都是将对象与创建过程分离,那么他们有什么区别呢?

  • 建造者模式更加注重方法的调用顺序,工厂模式注重创建对象
  • 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成
  • 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样

工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,定制化地创建不同的对象。也就是工厂模式是用来创建几种相似类型的对象,关注一种父类(接口类)下不同子类的创建;而建造者模式则是创建一种类型的但内部数据有不同表现形式的对象,关注一种类型不同对象的创建过程

总结一下

和工厂模式类似,建造者模式也致力于将对象的创建与使用分离。不同的是工厂模式的产品是具有抽象产品的,也就是具体产品可以有各自不同的方法实现,建造者模式的产品是固定的复杂对象,不同的具体建造者所能改变的也只是产品的组成部分属性值,产品的方法实现是固定的。但建造者模式拥有指挥者角色可以依据不同场景下的要求更好的调节产品内组成部分的依赖(顺序或种类)从而改变产品的特征。我感觉将二者结合起来应对复杂场景设计应该非常有帮助。

相关文章
|
6天前
|
设计模式 测试技术 Python
《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
【7月更文挑战第10天】Page Object Model (POM)是Selenium自动化测试中的设计模式,用于提高代码的可读性和维护性。POM将每个页面表示为一个类,封装元素定位和交互操作,使得测试脚本与页面元素分离。当页面元素改变时,只需更新对应页面类,减少了脚本的重复工作和维护复杂度,有利于团队协作。POM通过创建页面对象,管理页面元素集合,将业务逻辑与元素定位解耦合,增强了代码的复用性。示例展示了不使用POM时,脚本直接混杂了元素定位和业务逻辑,而POM则能解决这一问题。
24 6
|
4天前
|
设计模式 Java 测试技术
《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
【7月更文挑战第12天】在本文中,作者宏哥介绍了如何在不使用PageFactory的情况下,用Java和Selenium实现Page Object Model (POM)。文章通过一个百度首页登录的实战例子来说明。首先,创建了一个名为`BaiduHomePage1`的页面对象类,其中包含了页面元素的定位和相关操作方法。接着,创建了测试类`TestWithPOM1`,在测试类中初始化WebDriver,设置驱动路径,最大化窗口,并调用页面对象类的方法进行登录操作。这样,测试脚本保持简洁,遵循了POM模式的高可读性和可维护性原则。
12 2
|
5天前
|
设计模式 Java 测试技术
《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
【7月更文挑战第11天】页面对象模型(POM)通过Page Factory在Java Selenium测试中被应用,简化了代码维护。在POM中,每个网页对应一个Page Class,其中包含页面元素和相关操作。对比之下,非POM实现直接在测试脚本中处理元素定位和交互,代码可读性和可维护性较低。
|
7天前
|
设计模式 存储 缓存
Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
10 0
|
7天前
|
设计模式 缓存 安全
Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
7 0
|
7天前
|
设计模式 并行计算 安全
Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
12 0
|
7天前
|
设计模式 安全 Java
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
16 0
|
7天前
|
设计模式 Go
Go语言设计模式:使用Option模式简化类的初始化
在Go语言中,面对构造函数参数过多导致的复杂性问题,可以采用Option模式。Option模式通过函数选项提供灵活的配置,增强了构造函数的可读性和可扩展性。以`Foo`为例,通过定义如`WithName`、`WithAge`、`WithDB`等设置器函数,调用者可以选择性地传递所需参数,避免了记忆参数顺序和类型。这种模式提升了代码的维护性和灵活性,特别是在处理多配置场景时。
41 8
|
15天前
|
设计模式 JavaScript 前端开发
js设计模式【详解】—— 构造函数模式
js设计模式【详解】—— 构造函数模式
18 6
|
21天前
|
设计模式 存储 算法
设计模式学习心得之五种创建者模式(2)
设计模式学习心得之五种创建者模式(2)
17 2