Back

使用 JavaScript Builder 模式灵活创建对象

使用 JavaScript Builder 模式灵活创建对象

假设你有一个创建用户对象的函数。它最初有三个参数,然后增长到五个,再到七个。其中一半是可选的。调用者必须记住确切的顺序,一个位置错误就会悄无声息地产生一个损坏的对象。这就是 JavaScript Builder 模式要解决的问题。

核心要点

  • Builder 模式通过链式 setter 方法和最终的 build() 调用来逐步构造对象,取代了容易出错的位置参数列表。
  • 流式 API(每个 setter 返回 this)使调用处代码自文档化且与顺序无关。
  • 仅在对象具有多个可选参数、必填字段强制要求或验证规则时使用该模式。对于更简单的情况,使用对象字面量或工厂函数。
  • build() 方法是验证必填字段并返回一个独立于 builder 本身的干净冻结对象的唯一位置。

什么是 JavaScript Builder 模式?

Builder 模式是一种创建型设计模式,它逐步构造对象而不是一次性完成。你不需要将每个值都传递到单个构造函数调用中,而是链式调用 setter 方法,并通过 build() 步骤完成创建,该步骤会验证并返回完成的对象。

它不是通用解决方案。对于只有两三个定义明确字段的简单对象,普通对象字面量或工厂函数更简洁。Builder 模式在对象创建涉及以下情况时才有其价值:

  • 许多可选参数,其中顺序无关紧要
  • 验证规则必须在对象使用前运行
  • 必填字段需要在创建时强制执行
  • 多步骤构造,其中不应暴露中间状态

问题所在:构造函数污染

考虑这种常见模式:

// ❌ 难以阅读,容易混淆参数顺序
const request = new ApiRequest('GET', '/users', null, true, 5000, 'json')

六个位置参数。没有标签。没有验证。如果你交换两个值,不会有任何警告。

一个清晰的 JavaScript Builder 模式示例

这是一个基于类的实现,使用了 JavaScript 流式 API——每个 setter 返回 this,实现方法链式调用:

class ApiRequestBuilder {
  constructor() {
    this.method = 'GET'        // 合理的默认值
    this.url = null
    this.body = null
    this.timeout = 3000        // 默认超时时间
    this.responseType = 'json'
  }

  setMethod(method) {
    this.method = method
    return this
  }

  setUrl(url) {
    this.url = url
    return this
  }

  setBody(body) {
    this.body = body
    return this
  }

  setTimeout(ms) {
    this.timeout = ms
    return this
  }

  build() {
    if (!this.url) {
      throw new Error('URL is required')
    }
    // 返回一个普通的冻结对象——而不是 builder 本身
    return Object.freeze({
      method: this.method,
      url: this.url,
      body: this.body,
      timeout: this.timeout,
      responseType: this.responseType,
    })
  }
}

// 使用方式
const request = new ApiRequestBuilder()
  .setUrl('/api/users')
  .setMethod('POST')
  .setBody({ name: 'Alice' })
  .build()

每次调用都是自文档化的。验证在对象使用前在 build() 中运行。默认值会自动应用。注意 build() 返回的是一个冻结的普通对象——而不是 builder——这使结果保持干净并防止意外修改。

Builder 模式与更简单的替代方案对比

场景更好的方法
2-3 个必填字段,无验证对象字面量或工厂函数
可选字段,无规则通过 createUser({ name, age }) 使用命名参数
必填字段 + 验证 + 默认值Builder 模式
复杂的多步骤构造Builder 模式

createRequest({ url, method = 'GET' }) 这样的命名参数工厂函数可以干净地处理许多情况。当验证逻辑或顺序使该函数难以理解时,再使用 builder。

关于 TypeScript 的说明

TypeScript 可以使 builder 显著更安全。你可以使用条件类型或步骤式 builder 接口来强制要求只有在调用了必需的 setter 之后才能调用 build()。如果你的项目使用 TypeScript,这值得探索——但核心的 JavaScript 模式在没有它的情况下也能很好地工作。

总结

当对象创建有需要强制执行的规则时使用 Builder 模式,而不是将其作为默认的对象创建策略。流式 API 使调用处代码可读,build() 步骤使验证显式化,默认值减少了噪音。对于更简单的情况,工厂函数或普通对象字面量才是正确的工具。

常见问题

选项对象将命名参数分组,这解决了位置参数问题。Builder 添加了一个 build 步骤,在其中你可以验证必填字段、强制执行约束,并在使用前冻结结果。如果你需要这些保证,builder 更合适。如果你只需要带默认值的命名键,选项对象更简单。

可以,但要小心。调用 build 后,builder 仍然保留着上一次配置的状态。你必须重置每个字段或为每个对象创建一个新的 builder 实例。每次创建新实例是更安全、更可预测的方法。

可能会。如果你的对象只有几个众所周知的字段且没有验证规则,普通对象字面量或带命名参数的工厂函数更简洁。当可选字段数量增长、默认值相互影响或创建需要强制约束时,Builder 模式才会带来回报。

Object.freeze 防止返回对象的顶层属性在创建后被更改。这使构建的结果可预测且只读,当对象通过多层代码传递时尤其有用。它在 builder 内部的配置时间和外部的使用时间之间划定了清晰的界限。

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay