3.4 Builder 模式:从零搭建完整项目
3.4.1 问题的由来
在实际开发中,我们经常需要创建一些包含多个参数的对象,比如电脑配置、订单信息、HTTP 请求等。如果直接使用构造器,会出现参数列表过长、可读性差、容易传错顺序等问题。例如:
// 可读性极差,容易把内存和硬盘大小搞混
Computer computer = new Computer("i7-13700", 32, 1024, true, "NVIDIA RTX 4060", 750);
当某些参数是可选的时候,还必须提供大量重载构造器,或者使用空构造器后逐个调用 setter,导致对象状态不完整。
3.4.2 Builder 模式简介
Builder 模式(建造者模式)的核心思想是:
- 将对象的构造过程与表示分离;
- 通过一个静态内部 Builder 类收集参数;
- 使用链式调用设置各个属性;
- 最后通过
build()方法完成对象的创建,并在该方法中进行校验。
这样既能保证对象创建后是不可变的(线程安全),又提供了清晰的构造流程。
3.4.3 从零搭建项目
我们通过一个完整的 Java 项目来实践 Builder 模式。这里使用 Maven 管理项目,JDK 17。
① 项目结构
computer-builder/
├── pom.xml
└── src
├── main
│ └── java
│ └── com
│ └── example
│ └── computer
│ ├── Computer.java // 目标类(包含静态 Builder)
│ └── Main.java // 入口,演示用法
└── test
└── java
└── com
└── example
└── computer
└── ComputerTest.java // 单元测试
② pom.xml 最小配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>computer-builder</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 仅用于单元测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
3.4.4 实现 Computer 类与 Builder
假设我们要装配一台电脑,必选参数:CPU、内存大小、硬盘大小;可选参数:独立显卡、电源功率、是否带无线网卡等。
package com.example.computer;
/**
* 电脑类 - 使用 Builder 模式构造
* 所有字段均为 final,保证不可变性
*/
public class Computer {
// 必选参数
private final String cpu;
private final int ram; // GB
private final int storage; // GB
// 可选参数
private final String gpu;
private final int powerSupply; // W
private final boolean wifiEnabled;
// 私有构造器,只能通过 Builder 调用
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.storage = builder.storage;
this.gpu = builder.gpu;
this.powerSupply = builder.powerSupply;
this.wifiEnabled = builder.wifiEnabled;
}
// Getter 方法(省略其他 getter)
public String getCpu() { return cpu; }
public int getRam() { return ram; }
public int getStorage() { return storage; }
public String getGpu() { return gpu; }
public int getPowerSupply() { return powerSupply; }
public boolean isWifiEnabled() { return wifiEnabled; }
@Override
public String toString() {
return String.format(
"Computer [cpu=%s, ram=%dGB, storage=%dGB, gpu=%s, powerSupply=%dW, wifi=%b]",
cpu, ram, storage, gpu, powerSupply, wifiEnabled
);
}
/**
* 静态内部 Builder 类
*/
public static class Builder {
// 必选参数
private final String cpu;
private final int ram;
private final int storage;
// 可选参数 - 提供默认值
private String gpu = "集成显卡";
private int powerSupply = 500;
private boolean wifiEnabled = true;
// Builder 构造器,接收必选参数
public Builder(String cpu, int ram, int storage) {
// 简单校验
if (cpu == null || cpu.isBlank()) {
throw new IllegalArgumentException("CPU 不能为空");
}
if (ram <= 0 || storage <= 0) {
throw new IllegalArgumentException("内存和硬盘必须大于0");
}
this.cpu = cpu;
this.ram = ram;
this.storage = storage;
}
// 以下方法用于设置可选参数,返回 Builder 自身实现链式调用
public Builder gpu(String gpu) {
this.gpu = gpu;
return this;
}
public Builder powerSupply(int powerSupply) {
if (powerSupply < 300) {
throw new IllegalArgumentException("电源功率至少300W");
}
this.powerSupply = powerSupply;
return this;
}
public Builder wifiEnabled(boolean wifiEnabled) {
this.wifiEnabled = wifiEnabled;
return this;
}
// 构建最终对象
public Computer build() {
// 在这里进行跨参数校验(例如电源是否足够支持独显)
if (!"集成显卡".equals(gpu) && powerSupply < 500) {
throw new IllegalStateException("独立显卡建议电源≥500W");
}
return new Computer(this);
}
}
}
设计要点:
- 私有的全参构造器,保证对象只能通过
Builder.build()生成。 - Builder 的构造器只接收必选参数,在构造时就完成必填校验。
- 每个可选参数都有独立的 setter 方法(命名与字段同名),返回
Builder以支持链式调用。 - 所有默认值集中在 Builder 中,避免客户端重复设置。
build()方法中可以执行跨字段的复杂校验,确保生成的对象一定合法。
3.4.5 使用 Builder 创建对象
package com.example.computer;
public class Main {
public static void main(String[] args) {
// 1. 只配置必选参数,其他使用默认值
Computer officePC = new Computer.Builder("i5-13400", 16, 512)
.build();
System.out.println(officePC);
// 2. DIY 游戏主机,设置所有参数
Computer gamingPC = new Computer.Builder("i7-13700K", 32, 2048)
.gpu("RTX 4080")
.powerSupply(850)
.wifiEnabled(false) // 用有线网卡
.build();
System.out.println(gamingPC);
// 3. 不合理的电源设置会在 build() 时抛出异常
// Computer invalidPC = new Computer.Builder("i9", 32, 1024)
// .gpu("RTX 4090")
// .powerSupply(400)
// .build(); // 抛出 IllegalStateException
}
}
运行 Main 可以得到:
Computer [cpu=i5-13400, ram=16GB, storage=512GB, gpu=集成显卡, powerSupply=500W, wifi=true]
Computer [cpu=i7-13700K, ram=32GB, storage=2048GB, gpu=RTX 4080, powerSupply=850W, wifi=false]
优点一目了然:代码像写文章一样,每个参数的含义明确,顺序随意,不会搞混,而且校验在最终 build() 时集中执行,对象创建后即可放心使用。
3.4.6 单元测试(简单演示)
package com.example.computer;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ComputerTest {
@Test
void shouldCreateComputerWithDefaults() {
Computer pc = new Computer.Builder("i3-12100", 8, 256).build();
assertEquals("集成显卡", pc.getGpu());
assertTrue(pc.isWifiEnabled());
}
@Test
void shouldThrowExceptionWhenRamNegative() {
assertThrows(IllegalArgumentException.class, () -> {
new Computer.Builder("i5", -8, 512);
});
}
@Test
void shouldThrowExceptionWhenPowerTooLowForGpu() {
Computer.Builder builder = new Computer.Builder("i7", 16, 1024)
.gpu("RTX 3060")
.powerSupply(350);
assertThrows(IllegalStateException.class, builder::build);
}
}
在项目根目录下执行 mvn test,所有测试通过,确保 Builder 的实现正确。
3.4.7 适用场景与变体
何时使用 Builder 模式:
- 构造器参数超过 4 个,且有多个可选参数;
- 需要创建不可变对象(如配置类、请求对象、DTO);
- 构造过程中需要复杂的参数校验或参数间的依赖关系。
Lombok 简化:
如果你使用 Lombok,只需在类上添加 @Builder 注解即可自动生成 Builder 代码。不过,理解原生实现有助于更灵活地控制校验逻辑和默认值。示例:
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
@Builder
@Getter
@ToString
public class ComputerLombok {
private final String cpu;
private final int ram;
private final int storage;
@Builder.Default
private String gpu = "集成显卡";
@Builder.Default
private int powerSupply = 500;
@Builder.Default
private boolean wifiEnabled = true;
}
使用时直接 ComputerLombok.builder().cpu("i7").ram(16).storage(1024).build();
3.4.8 小结
祝贺你完成了 Builder 模式从零搭建完整项目的实战!通过这个例子,你掌握了:
- 如何将复杂对象的构建过程封装在静态内部类中;
- 链式调用的实现技巧;
- 参数默认值与多级校验的合理安排;
- 保持对象不可变带来的线程安全优势。
在后续章节中,我们还会看到 Builder 模式在构建 HTTP 客户端、生成复杂 SQL、配置解析等场景的应用。这个模式的本质是以可读性和安全性换取一点点代码量,对于长期维护的大型项目,收益远大于成本。