← 返回题目列表

什么是建造者模式?它解决什么问题?

高频 简单 第 1 / 26 题 更新于 2026/07/28
建造者模式创建型模式复杂对象设计模式

简化版

建造者模式是一种创建型设计模式,用来把复杂对象的创建过程拆成多个清晰步骤,最后统一生成对象。它主要解决构造参数过多、构造过程复杂、对象创建逻辑和业务代码混在一起的问题。

详细版

建造者模式适合创建“字段多、组合多、构建规则多”的对象。它不强调“创建哪一种产品”,而强调“怎样一步步把一个复杂产品组装出来”。

常见使用场景有:

  • 一个对象有很多可选参数,构造方法越写越长;
  • 对象创建前需要做参数校验、默认值填充、派生字段计算;
  • 创建过程需要按步骤表达,例如配置连接池、构建 HTTP 请求、组装复杂报表;
  • 希望创建出来的对象不可变,避免后续被随意修改;
  • 希望调用代码更可读,例如 builder().host(...).port(...).timeout(...).build()

典型结构一般包括:

  • Product:最终要创建的复杂对象;
  • Builder:负责分步骤设置属性和构建规则;
  • build():统一做校验、默认值处理,并返回最终对象;
  • Director:可选角色,用来封装固定构建流程,现代业务代码里不一定出现。

一个很典型的 Java 写法是:

public class HttpRequest {
    private final String url;
    private final String method;
    private final int timeoutMs;

    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.timeoutMs = builder.timeoutMs;
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private String url;
        private String method = "GET";
        private int timeoutMs = 3000;

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder method(String method) {
            this.method = method;
            return this;
        }

        public Builder timeoutMs(int timeoutMs) {
            this.timeoutMs = timeoutMs;
            return this;
        }

        public HttpRequest build() {
            if (url == null || url.isBlank()) {
                throw new IllegalArgumentException("url required");
            }
            return new HttpRequest(this);
        }
    }
}

调用方会变得比较清楚:

HttpRequest request = HttpRequest.builder()
        .url("https://example.com")
        .method("POST")
        .timeoutMs(5000)
        .build();

面试回答时可以强调:建造者模式不是为了少写代码,而是为了把复杂对象的创建过程表达清楚,并把构建规则集中到一个地方。

完整版教学

一、为什么需要建造者模式

最常见的起点是“构造方法爆炸”。比如一个对象有十几个字段,其中一部分必填,一部分可选,一部分有默认值,一部分还需要互相校验。刚开始可能会写成这样:

new Config("localhost", 8080, true, 3000, 10, null, false);

这种代码有三个问题。

第一,可读性差。调用方看到一串参数,很难知道 true300010 分别代表什么。

第二,容易传错。尤其是多个参数类型相同的时候,编译器无法帮你发现顺序错误。

第三,构建规则分散。默认值、合法性校验、字段依赖关系可能散落在多个构造方法或调用方里,后期维护成本会越来越高。

建造者模式把创建动作变成“带名字的步骤”:

Config config = Config.builder()
        .host("localhost")
        .port(8080)
        .sslEnabled(true)
        .timeoutMs(3000)
        .build();

这段代码看起来更长,但信息密度更高,维护风险更低。

二、建造者模式的核心思想

建造者模式的核心是“分离复杂对象的构建过程和最终表示”。

“构建过程”指的是:设置字段、应用默认值、检查参数、处理字段之间的依赖、按步骤组装对象。

“最终表示”指的是:创建完成后的对象本身。对象一旦被创建出来,最好只负责承载状态和提供业务行为,而不是到处暴露半成品状态。

因此,一个好的 Builder 通常会把这些逻辑收进去:

  • 必填字段检查;
  • 默认值设置;
  • 字段范围校验;
  • 字段组合合法性校验;
  • 最终对象创建。

这也是 build() 方法重要的原因。它不是简单 return new Product(...),而是构建过程的“收口点”。

三、它和普通构造方法有什么区别

普通构造方法适合字段少、规则简单的对象。例如:

new Point(10, 20);

这类对象用 Builder 反而啰嗦。

建造者模式适合字段多、可选项多、规则复杂的对象。例如 HTTP 请求、数据库连接配置、消息发送配置、搜索条件、报表导出参数等。

对比一下:

方式优点缺点适合场景
构造方法简单直接参数多时可读性差字段少、规则简单
JavaBean setter写法灵活对象可能处于半初始化状态框架绑定、简单 DTO
Builder可读、可校验、可构建不可变对象代码量更多复杂对象、可选参数多

所以建造者模式不是替代所有构造方法,而是解决“复杂创建”这个特定问题。

四、面试时容易漏掉的重点

很多人会把建造者模式答成“链式调用”。这是不完整的。链式调用只是常见写法,真正关键是构建过程的封装。

例如下面这种代码虽然是链式的,但不一定是严格意义上的 Builder:

user.setName("Tom").setAge(18);

如果它只是不断修改同一个可变对象,没有统一校验,没有最终 build() 收口,也没有解决复杂构建问题,那它更像链式 setter。

真正的 Builder 通常有一个独立的构建器对象,先收集构建参数,最后调用 build() 创建最终对象。

五、用七参数 HTTP 请求推导 Builder 的必要性

HTTP 请求同时包含 url、method、timeout、headers、body、TLS 和代理配置。直接构造器中的多个 String/int/boolean 容易错位,JavaBean 又会暴露只有 url 没有 method/body 的半成品;Builder 用命名步骤收集信息,再一次性交付合法请求。

HttpRequest.builder()
  .url("https://api.example.com/orders")
  .method(POST)
  .timeoutMs(5000)
  .header("Idempotency-Key", "K-42")
  .body(jsonBytes)
  .tlsEnabled(true)
build(): url 必填、timeout>0
build(): POST body 与 TLS 配置组合校验
build(): 复制 headers/body
new HttpRequest(validatedSnapshot)
调用方只看到完整成品

这条时间线把“可变构建阶段”和“稳定成品阶段”分开。Builder 可以反复接收参数,但 Product 只有在所有规则通过后才出现;若 Product 在第一步就被 new 出来再逐项修改,就仍然存在半初始化对象泄漏的窗口。

六、构建契约与对象不变量

评审维度本题结论
必填信息url 等执行请求不可缺少的信息。
默认值method=GET、timeout=3000 等在 Builder 中集中声明。
跨字段规则method/body、TLS/证书、proxy host/port 等组合在 build 检查。
可变引用处理headers Map 与 body 数组构建时复制,防止调用方后改。
Builder 生命周期Builder 是单次请求配置施工区,Product 创建后与其脱离。
Product 交付保证请求对象字段稳定,无法在发送途中被 setter 改成另一套配置。

build() 不是形式上的结束标记,而是对象从“参数集合”变成“合法业务值”的原子边界。单字段输入可以提前拒绝明显错误,跨字段规则必须等信息齐全后判断,Product 私有构造器还应保留必要兜底,避免未来新增创建入口绕过不变量。

七、与构造器、JavaBean 和工厂的选择边界

  • 字段少且全部必填时,短构造器或 record 通常比 Builder 更直接。
  • 字段多、可选项多、同类型参数易错时,命名步骤能显著提升调用可读性。
  • JavaBean setter 适合某些框架绑定,但对象可能在设置完成前就被观察到。
  • Builder 关注“同一种复杂对象怎样组装”;工厂模式关注“创建哪一种产品实现”。
  • 两者可以组合:工厂选择具体产品族或 Builder,Builder 再完成复杂组装。
  • 经典 Director 只有在固定步骤序列需要复用或存在多种表示时才有价值。
  • 链式 return this 只是语法,校验、默认值、拷贝和收口才是设计语义。
  • 若 Builder 比 Product 规则还复杂,应重新拆分对象职责,而不是继续堆方法。

Point(x,y) 这类短而清晰的值对象不需要 Builder;HTTP 请求这类多可选项和组合规则对象才匹配模式动机。

八、交付前的代码与测试检查

  1. 缺少每个必填字段分别调用 build(),应得到明确且稳定的异常。
  2. 完全不设置可选字段,核对默认值来自唯一权威位置。
  3. 传入最小值、最大值和越界值,确认范围判断没有反向或 off-by-one。
  4. 构造两个互相冲突的字段组合,确认只在信息完整时执行跨字段校验。
  5. 传入集合或数组后修改原引用,已构建 Product 不应跟着变化。
  6. 若 getter 返回可变数据,再尝试修改返回值,内部状态仍应保持不变。
  7. 连续调用同一个 Builder 两次,确认语义是明确允许复制还是文档明确禁止复用。
  8. 两线程共享一个 Builder 做压力测试应被禁止或证明安全,不能靠偶然结果。
  9. 若使用 Lombok,检查生成代码、默认值、@Singular、构造器可见性和框架兼容。
  10. 若迁移旧 API,比较默认值、异常类型、序列化字段和所有旧调用点行为。

记忆钩子:字段多只是信号,真正决定使用 Builder 的是命名步骤、不变量和成品边界。

九、常见误区与追问

  • 误区:建造者模式的定义就是链式调用。 链式是常见语法,核心是分离构建过程与最终表示。
  • 误区:Builder 一定比构造器更少代码。 它通常增加代码,换取可读性、校验和演进能力。
  • 误区:build 可以返回仍需 setter 补全的对象。 这会破坏统一交付合法成品的边界。
  • 追问:Builder 适合哪些对象? 字段/可选项多、组合规则复杂、希望不可变或构建步骤需复用的对象。
  • 追问:Director 是否属于必选角色? 不是;现代链式 Builder 常由客户端直接组织步骤。
  • 追问:Builder 与 Factory 最大区别? Builder 关心同一复杂对象怎样组装,Factory 关心选择哪种产品。

十、加强记忆

建造者模式的记忆锚点是:对象字段少,用构造方法;对象字段多、可选项多、规则多,就把创建过程交给 Builder。它的价值不在于“写起来酷”,而在于让复杂对象的创建更可读、更安全、更集中。