讲解

点一碗兰州拉面:粗细九种、加不加辣、加不加肉、要不要香菜蒜苗——如果店家让你把这些全写在一个参数串里,点单会错得一塌糊涂;实际是师傅按「面 → 汤 → 辣 → 肉 → 菜」一步步装配。建造者模式就是把这个「一步步装配复杂对象」的过程显式化:把构造逻辑从对象本身抽出来,放进一个专门的建造者,链式调用一个个设置,最后 build() 出成品。

它要解决的核心痛点是「伸缩构造函数」:一个类有十几个可选参数,构造函数就会爆炸出无数种重载组合;退化成十几个可选参数的对象字面量后,又丢了「哪些组合合法」「顺序是什么」的表达力。建造者用方法链把每个参数变成一个有名字、有校验、可复用的调用点。

GoF 的原版结构里还有一个 Director(指挥者)角色:它封装「标准装配顺序」,比如「导出报表 = 加表头 + 加数据行 + 加页脚」,客户端一句话就能拿到标准成品。实践中 Director 常被省略,链式建造者本身已覆盖大部分需求——流式 API(fluent API)就是这个模式的日常形态,各类 HTTP 客户端、查询构造器都是它的化身。

和抽象工厂的对比值得记牢:抽象工厂一次调用返回一族配套产品;建造者把「一个」复杂对象的创建拆成多步,你可以中途查看、条件化地决定加不加某个部件。前者关心「族」,后者关心「过程」。

示例

一个 HTTP 请求构造器:链式设置方法、请求头、JSON 体,build() 产出不可变的请求对象:

import assert from 'node:assert/strict';

interface HttpRequest {
  readonly url: string;
  readonly method: string;
  readonly headers: Readonly<Record<string, string>>;
  readonly body: string | null;
}

class HttpRequestBuilder {
  private method = 'GET';
  private headers: Record<string, string> = {};
  private body: string | null = null;

  constructor(private readonly url: string) {}

  post(): this {
    this.method = 'POST';
    return this;
  }

  header(name: string, value: string): this {
    this.headers[name] = value;
    return this;
  }

  json(data: unknown): this {
    this.headers['Content-Type'] = 'application/json';
    this.body = JSON.stringify(data);
    return this;
  }

  build(): HttpRequest {
    return { url: this.url, method: this.method, headers: { ...this.headers }, body: this.body };
  }
}

// 默认值生效:不传任何选项就是一个普通 GET
const simple = new HttpRequestBuilder('https://api.example.com/users').build();
assert.equal(simple.method, 'GET');
assert.equal(simple.body, null);

// 链式装配一个 POST
const create = new HttpRequestBuilder('https://api.example.com/users')
  .post()
  .header('Authorization', 'Bearer token-123')
  .json({ name: '小林' })
  .build();
assert.equal(create.method, 'POST');
assert.equal(create.headers['Content-Type'], 'application/json');
assert.equal(create.body, '{"name":"小林"}');

console.log(create.method + ' ' + create.url + ' body=' + create.body);

三个细节值得品:每个方法返回 this 才能串成链;build() 里对 headers 做了浅拷贝,成品对象不再受建造者后续修改的影响;json() 内部顺手设置了 Content-Type——建造者把「这些参数总是成对出现」的知识封装进了方法,调用方不会忘。

常见坑

  • 建造者里不做任何校验:把必填检查、组合合法性检查堆到 build() 里一次做掉,是建造者的重要价值;什么都不检查就只是个花哨的对象字面量。
  • 复用同一个建造者实例:建造者是有状态的,build() 之后接着用会把上一个对象的设置带进下一个;要么每次新建,要么 build() 里重置。
  • 三个参数也上建造者:参数少且必填时构造函数更直接;建造者的价值随可选参数数量增长。
  • 忘记成品不可变:build() 应该产出尽量不可变的对象(readonly + 拷贝),否则「建造完又被人改」会让链式装配的确定性荡然无存。
  • 链式调用里藏副作用:每个设置方法应该只改自己的状态,顺手发请求、写日志的建造者会让调用方无法预测行为。

小结

建造者把复杂对象的装配拆成有名字的步骤,链式调用 + build() 产出不可变成品;它克制伸缩构造函数,也常省略 GoF 原版的 Director。下一章换个思路:不从头装配,而是拿一个现成对象「复印」——原型模式。