文章目录
- 一、概述
-
- [1.1 结构与角色](#1.1 结构与角色)
- [1.2 适用场景](#1.2 适用场景)
- 二、实现方式
-
- [2.1 传统建造者](#2.1 传统建造者)
- [2.2 链式调用建造者](#2.2 链式调用建造者)
- [2.3 传统建造者 vs 链式调用建造者](#2.3 传统建造者 vs 链式调用建造者)
- [三、JDK 源码中的建造者](#三、JDK 源码中的建造者)
-
- [3.1 StringBuilder](#3.1 StringBuilder)
- [3.2 StringBuffer](#3.2 StringBuffer)
- [3.3 Lombok @Builder](#3.3 Lombok @Builder)
- 四、总结
一、概述
在日常开发中,我们经常会遇到需要构建复杂对象的场景。比如一个 Computer 对象,它可能有 CPU、内存、硬盘、显卡、显示器、键盘、鼠标等众多属性,而且其中某些属性是必填的,某些是选填的,不同配置组合可以构建出不同规格的电脑。
如果直接使用构造方法来创建这样的对象,就会面临以下问题:
- 构造参数过多:一个类有十几个属性,构造方法的参数列表会非常长,可读性极差
- 参数顺序容易混淆:多个同类型参数(如
String cpu, String gpu)很容易传错位置,编译器不会报错 - 部分属性可选时需要多个重载构造方法:为了应对不同的参数组合,需要提供大量的构造方法重载,形成"伸缩构造方法"反模式
比如下面的代码:
java
// 构造参数过多,可读性差,容易传错
Computer computer = new Computer("Intel i7", "16GB", "512GB SSD",
"RTX 3060", "27inch", "机械键盘", "无线鼠标", "WiFi6", "蓝牙5.0");
这时候,建造者模式(Builder Pattern)就派上用场了。
建造者模式是 Java 中最常用的设计模式之一,这种类型的设计模式属于创建型模式,它将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
简单来说,建造者模式就是将复杂对象的创建过程封装起来,允许客户端通过一步步地指定复杂对象的类型和内容来构建它,而不需要知道内部的构建细节。
核心:将复杂对象的构建过程与表示分离,使得同样的构建过程可以创建不同的表示
1.1 结构与角色
建造者模式包含以下角色:
构建
实现
创建
调用
Director 指挥者
Builder 抽象建造者
Product 产品角色
ConcreteBuilder 具体建造者
- Product(产品角色):被构建的复杂对象,包含多个组成部分
- Builder(抽象建造者) :定义创建产品各个部件的抽象接口,通常还包含一个返回最终产品的方法
getResult() - ConcreteBuilder(具体建造者):实现 Builder 接口,构建和装配各个部件,定义并明确它所创建的表示,提供一个获取产品对象的方法
- Director(指挥者):构建一个使用 Builder 接口的对象,它负责控制产品的构建顺序和流程,隔离了客户端与创建过程
1.2 适用场景
- 需要创建的对象具有较多的属性,且部分属性是可选的
- 需要创建的对象需要经过多个步骤来构建,且构建过程是稳定的
- 需要创建不同的表示形式(不同配置组合)的复杂对象
- 希望避免"伸缩构造方法"反模式
二、实现方式
2.1 传统建造者
传统建造者模式严格按照 GoF 的定义,包含产品、抽象建造者、具体建造者和指挥者四个角色。
以组装电脑为例,电脑有 CPU、内存、硬盘、显卡等部件,不同配置可以组装出不同规格的电脑:
(1)产品角色------电脑
java
/**
* 产品角色:电脑
*/
public class Computer {
private String cpu;
private String memory;
private String hardDisk;
private String gpu;
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getHardDisk() {
return hardDisk;
}
public void setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
}
public String getGpu() {
return gpu;
}
public void setGpu(String gpu) {
this.gpu = gpu;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", memory='" + memory + '\'' +
", hardDisk='" + hardDisk + '\'' +
", gpu='" + gpu + '\'' +
'}';
}
}
(2)抽象建造者
java
/**
* 抽象建造者:电脑建造者
*/
public abstract class ComputerBuilder {
/**
* 构建 CPU
*/
public abstract void buildCpu();
/**
* 构建内存
*/
public abstract void buildMemory();
/**
* 构建硬盘
*/
public abstract void buildHardDisk();
/**
* 构建显卡
*/
public abstract void buildGpu();
/**
* 获取构建好的电脑对象
*
* @return 电脑对象
*/
public abstract Computer getResult();
}
(3)具体建造者------高配电脑建造者
java
/**
* 具体建造者:高配电脑建造者
*/
public class HighEndComputerBuilder extends ComputerBuilder {
private final Computer computer = new Computer();
@Override
public void buildCpu() {
computer.setCpu("Intel i9-13900K");
}
@Override
public void buildMemory() {
computer.setMemory("32GB DDR5");
}
@Override
public void buildHardDisk() {
computer.setHardDisk("1TB NVMe SSD");
}
@Override
public void buildGpu() {
computer.setGpu("RTX 4090");
}
@Override
public Computer getResult() {
return computer;
}
}
(4)具体建造者------低配电脑建造者
java
/**
* 具体建造者:低配电脑建造者
*/
public class LowEndComputerBuilder extends ComputerBuilder {
private final Computer computer = new Computer();
@Override
public void buildCpu() {
computer.setCpu("Intel i3-12100");
}
@Override
public void buildMemory() {
computer.setMemory("8GB DDR4");
}
@Override
public void buildHardDisk() {
computer.setHardDisk("256GB SSD");
}
@Override
public void buildGpu() {
computer.setGpu("集成显卡");
}
@Override
public Computer getResult() {
return computer;
}
}
(5)指挥者
java
/**
* 指挥者:电脑组装指挥者
* 负责控制构建流程和顺序
*/
public class ComputerDirector {
/**
* 组装电脑,控制构建顺序
*
* @param builder 电脑建造者
* @return 组装好的电脑
*/
public Computer construct(ComputerBuilder builder) {
builder.buildCpu();
builder.buildMemory();
builder.buildHardDisk();
builder.buildGpu();
return builder.getResult();
}
}
(6)客户端调用
java
public class TraditionalBuilderDemo {
public static void main(String[] args) {
// 创建指挥者
ComputerDirector director = new ComputerDirector();
// 组装高配电脑
ComputerBuilder highEndBuilder = new HighEndComputerBuilder();
Computer highEndComputer = director.construct(highEndBuilder);
System.out.println(highEndComputer);
// Computer{cpu='Intel i9-13900K', memory='32GB DDR5', hardDisk='1TB NVMe SSD', gpu='RTX 4090'}
// 组装低配电脑
ComputerBuilder lowEndBuilder = new LowEndComputerBuilder();
Computer lowEndComputer = director.construct(lowEndBuilder);
System.out.println(lowEndComputer);
// Computer{cpu='Intel i3-12100', memory='8GB DDR4', hardDisk='256GB SSD', gpu='集成显卡'}
}
}
说明:传统建造者模式中,指挥者(Director)负责控制构建流程和顺序,客户端只需要告诉指挥者用哪个建造者即可,无需关心具体的构建过程。当需要更换配置时,只需要更换具体建造者。
2.2 链式调用建造者
传统建造者模式虽然结构清晰,但类较多,使用起来比较繁琐。在实际开发中,更常用的是链式调用建造者(也称为简化版建造者),它将建造者作为产品的静态内部类,通过链式调用来设置属性,代码更简洁、可读性更强。
(1)产品 + 内部建造者
java
/**
* 产品角色:手机
* 内部包含一个静态建造者类
*/
public class Phone {
private String cpu;
private String memory;
private String storage;
private String screen;
private String battery;
/**
* 私有构造方法,只能通过 Builder 创建
*/
private Phone(Builder builder) {
this.cpu = builder.cpu;
this.memory = builder.memory;
this.storage = builder.storage;
this.screen = builder.screen;
this.battery = builder.battery;
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", memory='" + memory + '\'' +
", storage='" + storage + '\'' +
", screen='" + screen + '\'' +
", battery='" + battery + '\'' +
'}';
}
/**
* 静态内部建造者
*/
public static class Builder {
private String cpu;
private String memory;
private String storage;
private String screen;
private String battery;
/**
* 设置 CPU
*
* @param cpu CPU 型号
* @return Builder 对象本身,支持链式调用
*/
public Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}
/**
* 设置内存
*
* @param memory 内存大小
* @return Builder 对象本身
*/
public Builder memory(String memory) {
this.memory = memory;
return this;
}
/**
* 设置存储
*
* @param storage 存储大小
* @return Builder 对象本身
*/
public Builder storage(String storage) {
this.storage = storage;
return this;
}
/**
* 设置屏幕
*
* @param screen 屏幕参数
* @return Builder 对象本身
*/
public Builder screen(String screen) {
this.screen = screen;
return this;
}
/**
* 设置电池
*
* @param battery 电池容量
* @return Builder 对象本身
*/
public Builder battery(String battery) {
this.battery = battery;
return this;
}
/**
* 构建手机对象
*
* @return 手机对象
*/
public Phone build() {
// 可以在此处进行参数校验
if (cpu == null || cpu.isEmpty()) {
throw new IllegalStateException("CPU 不能为空");
}
if (memory == null || memory.isEmpty()) {
throw new IllegalStateException("内存不能为空");
}
return new Phone(this);
}
}
}
(2)客户端调用
java
public class ChainBuilderDemo {
public static void main(String[] args) {
// 链式调用构建手机对象
Phone phone = new Phone.Builder()
.cpu("骁龙8 Gen3")
.memory("16GB")
.storage("512GB")
.screen("6.7英寸 AMOLED")
.battery("5000mAh")
.build();
System.out.println(phone);
// Phone{cpu='骁龙8 Gen3', memory='16GB', storage='512GB', screen='6.7英寸 AMOLED', battery='5000mAh'}
// 也可以只设置必填属性,选填属性不设置
Phone simplePhone = new Phone.Builder()
.cpu("天玑9200")
.memory("8GB")
.build();
System.out.println(simplePhone);
// Phone{cpu='天玑9200', memory='8GB', storage='null', screen='null', battery='null'}
}
}
链式调用建造者的优势非常明显:
- 可读性强:每个方法调用都清楚地表明了设置的属性名称和值
- 参数顺序无关:不用关心参数的顺序,不会传错
- 可选属性灵活 :不需要的属性直接不调用对应方法即可,无需传递
null - 支持参数校验 :可以在
build()方法中统一进行参数校验 - 不可变对象 :产品类的构造方法是私有的,属性可以声明为
final,创建后不可修改
2.3 传统建造者 vs 链式调用建造者
| 对比维度 | 传统建造者 | 链式调用建造者 |
|---|---|---|
| 类的个数 | 多(产品 + 抽象建造者 + 具体建造者 + 指挥者) | 少(产品 + 内部 Builder) |
| 指挥者 | 有,控制构建流程 | 无,客户端自行链式调用 |
| 可读性 | 一般 | 强(链式调用语义清晰) |
| 扩展性 | 好(新增具体建造者即可) | 一般(新增产品需新建 Builder) |
| 适用场景 | 需要多种表示形式的复杂产品 | 属性较多的单个产品 |
| 典型应用 | 框架设计 | 日常业务开发 |
选型建议:
- 如果需要构建同一产品的多种不同表示(如不同配置的电脑),使用传统建造者
- 如果只是需要创建属性较多的对象,使用链式调用建造者即可,代码更简洁
三、JDK 源码中的建造者
建造者模式在 JDK 及主流框架中有着广泛的应用,下面来看几个经典的例子。
3.1 StringBuilder
java.lang.StringBuilder 就是一个非常典型的建造者模式的应用,它通过 append() 方法逐步构建字符串,最终通过 toString() 方法获取构建结果。
java
// StringBuilder 的建造者模式应用
public final class StringBuilder extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
/**
* 构造方法
*/
public StringBuilder() {
super(16);
}
/**
* 追加字符串 ------ 相当于建造者的 buildPart() 方法
*/
@Override
public StringBuilder append(String str) {
super.append(str);
return this; // 返回自身,支持链式调用
}
/**
* 追加整数
*/
@Override
public StringBuilder append(int i) {
super.append(i);
return this;
}
/**
* 获取最终结果 ------ 相当于建造者的 getResult() 方法
*/
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
}
使用方式:
java
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append("World").append(2024);
String result = sb.toString(); // "Hello World2024"
说明 :
StringBuilder中的append()方法返回this,这就是链式调用的关键。append()相当于建造者的buildPart()方法,toString()相当于getResult()方法。
3.2 StringBuffer
java.lang.StringBuffer 与 StringBuilder 类似,区别在于 StringBuffer 的方法是线程安全的(加了 synchronized 关键字)。
java
public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
@Override
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
@Override
public synchronized String toString() {
return new String(value, 0, count);
}
}
3.3 Lombok @Builder
在日常开发中,我们经常使用 Lombok 的 @Builder 注解来快速生成建造者模式代码,其原理就是在编译期自动生成内部 Builder 类。
使用 Lombok @Builder 注解:
java
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class User {
private String name;
private Integer age;
private String email;
private String phone;
}
Lombok 编译后自动生成的等价代码(简化版):
java
public class User {
private String name;
private Integer age;
private String email;
private String phone;
User(String name, Integer age, String email, String phone) {
this.name = name;
this.age = age;
this.email = email;
this.phone = phone;
}
public static UserBuilder builder() {
return new UserBuilder();
}
public static class UserBuilder {
private String name;
private Integer age;
private String email;
private String phone;
UserBuilder() {}
public UserBuilder name(String name) {
this.name = name;
return this;
}
public UserBuilder age(Integer age) {
this.age = age;
return this;
}
public UserBuilder email(String email) {
this.email = email;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public User build() {
return new User(name, age, email, phone);
}
}
}
客户端使用:
java
User user = User.builder()
.name("张三")
.age(25)
.email("zhangsan@example.com")
.build();
// User(name=张三, age=25, email=zhangsan@example.com, phone=null)
可以看到,Lombok 帮我们省去了大量样板代码,让建造者模式的使用变得非常简单。
四、总结
建造者模式的核心思想是将复杂对象的构建过程与表示分离,使得同样的构建过程可以创建不同的表示,客户端不需要知道对象的具体构建细节。
优点:
- 封装性好:客户端不需要知道产品内部的组成细节,将产品本身与产品的创建过程解耦
- 可读性强:链式调用方式使得代码语义清晰,容易理解
- 灵活可控:可以精细地控制对象的创建过程,支持参数校验和默认值
- 符合开闭原则(传统建造者):新增具体建造者无需修改已有代码
缺点:
- 类的个数增加:需要额外创建 Builder 类,增加了系统的类的数量
- 产品内部变化时建造者需同步修改:如果产品的属性发生变化,建造者也需要相应修改
- 传统建造者模式结构较复杂,对于简单对象有些"杀鸡用牛刀"
适用场景:
- 对象有较多的属性,且部分属性是可选的
- 需要创建的对象需要经过多个步骤来构建,且构建过程需要控制
- 需要创建不同表示形式的复杂对象
- 希望避免"伸缩构造方法"反模式
- 希望创建不可变对象
创建型模式小结:至此,5 种创建型模式已经全部介绍完毕------单例模式保证唯一实例、工厂方法模式将创建逻辑委托给子类、抽象工厂模式创建产品族、建造者模式分步构建复杂对象、原型模式通过克隆创建对象。它们各有侧重,根据实际场景灵活选择即可。
参考博客:
建造者模式 | 菜鸟教程:https://www.runoob.com/design-pattern/builder-pattern.html