人人都会AI编程

3.4 Builder 模式:从零搭建完整项目

更新时间:2026-06-29

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、配置解析等场景的应用。这个模式的本质是以可读性和安全性换取一点点代码量,对于长期维护的大型项目,收益远大于成本。