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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

javascript 复制代码
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 设计者的温柔。

相关推荐
折哥的程序人生 · 物流技术专研13 小时前
第8篇:六大设计原则在工厂模式中的体现
java·设计模式·设计原则·工厂模式·编程进阶·扩充系列
咖啡八杯15 小时前
GoF设计模式——模板方法模式
java·后端·spring·设计模式
饼干哥哥1 天前
Openclaw已死?任何Agent都能用Cli实现AI自主运营跨境电商
设计模式·openai·ai编程
折哥的程序人生 · 物流技术专研1 天前
第4篇:抽象工厂模式——产品族一键创建
java·设计模式·抽象工厂模式·创建型模式·编程进阶·产品族·扩充系列
geovindu2 天前
go: Cascading Failures Pattern
开发语言·后端·设计模式·golang·反模式·级联失效反模式
端庄的战斗机2 天前
javascript 设计模式(文章很长,请自备瓜子,水果和眼药水)
开发语言·javascript·设计模式
饼干哥哥2 天前
我开发了一个「吃掉」别人Skill来升级的饕餮.Skill,现在免费开源
人工智能·设计模式·开源
CaffeinePro2 天前
什么是系统架构?架构师到底在做什么?
设计模式·架构
AI人工智能+电脑小能手2 天前
【大白话说Java面试题 第156题】【06_Spring篇】第16题:Spring 用到了哪些设计模式?
java·spring·设计模式·代理模式·工厂模式
前端百草阁2 天前
JavaScript 设计模式(23 种)
开发语言·前端·javascript·设计模式