不知道你有没有接手过那种"祖传代码",里面有一个极其庞大的类,初始化的时候需要传十几个参数。每次调用它,你都得小心翼翼地数逗号: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 条行动建议
-
参数超过 4 个就该警惕了 :如果你的构造函数参数超过 4 个,或者有好几个参数是可选的(经常传
null),别犹豫,马上换成建造者模式,或者至少用"配置对象"传参。 -
把校验逻辑放在 build 里 :这是建造者模式最大的隐藏红利。你可以在
build()方法里统一检查"A 属性存在时 B 属性是否也存在",保证产出的对象永远是合法的。 -
JS 的"配置对象"其实是简化版 :在 JS 里,我们经常直接传一个对象
{ url: '...', method: '...' }。这其实是建造者模式的一种"变体"。但如果你需要复杂的构建逻辑(比如根据 A 参数自动计算 B 参数),标准的 Builder 类还是更清晰。
我以前总觉得多写一个 Builder 类是增加代码量。 后来在一次重构中,我把一个 12 个参数的初始化函数改成了 Builder,那天下午我看着那段清晰的代码,心里那个舒坦。
代码是写给人看的,顺便给机器运行。 让调用者用得舒服,是你作为 API 设计者的温柔。