普通视图

发现新文章,点击刷新页面。
昨天 — 2026年3月2日首页

04 | 别再写几十个参数的构造函数了——建造者模式

2026年3月2日 17:14

不知道你有没有接手过那种“祖传代码”,里面有一个极其庞大的类,初始化的时候需要传十几个参数。每次调用它,你都得小心翼翼地数逗号:new User('张三', null, true, 18, null, 'admin', ...)

一旦中间少传了一个 null,或者把第 5 个参数和第 6 个参数搞反了,整个程序直接报错,查都查不出来。 我以前写这种代码的时候,自己都觉得心虚,生怕哪天把自己给坑了。

今天咱们聊的这个建造者模式(Builder Pattern),就是专门为了解决这种“参数地狱”而生的。

为什么我们总是被“参数列表”搞得晕头转向?

说白了,这种长参数列表的问题,在于我们试图一口吃成个胖子。 我们想在实例化的一瞬间,把所有属性都塞进去。

但这违背了人类的认知习惯。 想象一下你去赛百味(Subway)买三明治。 你不会一进门就冲着店员喊一串代码:“我要全麦面包加火腿加生菜去洋葱加蛋黄酱烤热带走!” 店员肯定懵圈。

正确的流程是分步骤: 先选面包,再选肉,然后选配菜,最后选酱料。 每一步都是独立的,你可以选,也可以不选。

建造者模式的底层逻辑就是:把一个复杂对象的“构建过程”和它的“部件”分离。 不再是一次性 new 出来,而是通过一个专门的“建造者”,一步一步地把对象组装起来。

怎么把代码写得像“点菜”一样优雅?

在 JavaScript 里,我们可以利用链式调用(Chaining),把这个模式实现得非常漂亮。

假设我们要创建一个复杂的 Request 对象,用来发网络请求。

如果不适用模式,代码是这样的:

// 参数太多,根本记不住哪个位置是干啥的
// 第三个参数是 timeout 还是 headers?完全靠猜
const req = new HttpRequest('https://api.com', 'POST', null, 5000, { 'Content-Type': 'json' });

现在,我们用建造者模式改造一下:

class RequestBuilder {
  constructor(url) {
    this.url = url;
    this.method = 'GET'; // 默认值
    this.headers = {};
    this.body = null;
  }

  setMethod(method) {
    this.method = method;
    return this; // 关键:返回 this,实现链式调用
  }

  setHeader(key, value) {
    this.headers[key] = value;
    return this;
  }

  setBody(data) {
    this.body = JSON.stringify(data);
    return this;
  }

  // 最后一步:产出真正的对象
  build() {
    // 这里还可以加校验逻辑,比如:如果是 POST,必须有 body
    if (this.method === 'POST' && !this.body) {
      throw new Error('POST 请求必须有 Body');
    }
    return {
      url: this.url,
      method: this.method,
      headers: this.headers,
      body: this.body
    };
  }
}

// 使用起来就像写文章一样流畅
const request = new RequestBuilder('https://api.com')
  .setMethod('POST')
  .setHeader('Authorization', 'Bearer xxx')
  .setBody({ name: '小美' })
  .build();

两种写法的直观对比

容易出问题的写法: new Class(a, b, c, d, e...) 后果:代码可读性极差,维护者必须对着文档数参数位置。如果中间要插入一个新参数,所有调用方都得改。

更稳健的建造者写法: .setA().setB().build() 后果:代码本身就是文档,读起来像英语句子。参数顺序无所谓,不想传的参数直接跳过,用默认值即可。

给你的 3 条行动建议

  1. 参数超过 4 个就该警惕了:如果你的构造函数参数超过 4 个,或者有好几个参数是可选的(经常传 null),别犹豫,马上换成建造者模式,或者至少用“配置对象”传参。

  2. 把校验逻辑放在 build 里:这是建造者模式最大的隐藏红利。你可以在 build() 方法里统一检查“A 属性存在时 B 属性是否也存在”,保证产出的对象永远是合法的。

  3. JS 的“配置对象”其实是简化版:在 JS 里,我们经常直接传一个对象 { url: '...', method: '...' }。这其实是建造者模式的一种“变体”。但如果你需要复杂的构建逻辑(比如根据 A 参数自动计算 B 参数),标准的 Builder 类还是更清晰。

我以前总觉得多写一个 Builder 类是增加代码量。 后来在一次重构中,我把一个 12 个参数的初始化函数改成了 Builder,那天下午我看着那段清晰的代码,心里那个舒坦。

代码是写给人看的,顺便给机器运行。 让调用者用得舒服,是你作为 API 设计者的温柔。

❌
❌