Java设计模式-建造者模式

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

一、建造者模式介绍

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.StringBuilderjava.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 重学设计模式》

目录
相关文章
|
21天前
|
设计模式 Java 开发者
设计模式揭秘:Java世界的七大奇迹
【4月更文挑战第7天】探索Java设计模式:单例、工厂方法、抽象工厂、建造者、原型、适配器和观察者,助你构建健壮、灵活的软件系统。了解这些模式如何提升代码复用、可维护性,以及在特定场景下的应用,如资源管理、接口兼容和事件监听。掌握设计模式,但也需根据实际情况权衡,打造高效、优雅的软件解决方案。
|
22天前
|
设计模式 存储 Java
23种设计模式,享元模式的概念优缺点以及JAVA代码举例
【4月更文挑战第6天】享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享技术有效地支持大量细粒度对象的重用。这个模式在处理大量对象时非常有用,特别是当这些对象中的许多实例实际上可以共享相同的状态时,从而可以减少内存占用,提高程序效率
35 4
|
22天前
|
设计模式 Java 中间件
23种设计模式,适配器模式的概念优缺点以及JAVA代码举例
【4月更文挑战第6天】适配器模式(Adapter Pattern)是一种结构型设计模式,它的主要目标是让原本由于接口不匹配而不能一起工作的类可以一起工作。适配器模式主要有两种形式:类适配器和对象适配器。类适配器模式通过继承来实现适配,而对象适配器模式则通过组合来实现
32 4
|
21天前
|
设计模式 监控 Java
设计模式 - 观察者模式(Observer):Java中的战术与策略
【4月更文挑战第7天】观察者模式是构建可维护、可扩展系统的关键,它在Java中通过`Observable`和`Observer`实现对象间一对多的依赖关系,常用于事件处理、数据绑定和同步。该模式支持事件驱动架构、数据同步和实时系统,但需注意避免循环依赖、控制通知粒度,并关注性能和内存泄漏问题。通过明确角色、使用抽象和管理观察者注册,可最大化其效果。
|
20小时前
|
设计模式 消息中间件 Java
Java 设计模式:探索发布-订阅模式的原理与应用
【4月更文挑战第27天】发布-订阅模式是一种消息传递范式,被广泛用于构建松散耦合的系统。在 Java 中,这种模式允许多个对象监听和响应感兴趣的事件。
8 2
|
20小时前
|
设计模式 算法 Java
Java 设计模式:探索策略模式的概念和实战应用
【4月更文挑战第27天】策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在 Java 中,策略模式通过定义一系列的算法,并将每一个算法封装起来,并使它们可以互换,这样算法的变化不会影响到使用算法的客户。
6 1
|
1天前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
|
4天前
|
设计模式 算法 Java
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
|
4天前
|
设计模式 JavaScript Java
[设计模式Java实现附plantuml源码~行为型] 对象状态及其转换——状态模式
[设计模式Java实现附plantuml源码~行为型] 对象状态及其转换——状态模式
|
4天前
|
设计模式 存储 JavaScript
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式