Appearance
3.2 建造者模式 (Builder Pattern)
如果说工厂模式是“一键生产”,原型模式是“一键克隆”,那么建造者模式就是“精工细作的自定义装配”。
核心定义:将一个复杂对象的构建过程与其最终表示相分离,使得同样的构建过程可以创建出不同的表示。
这个模式主要用于创建那些构造过程本身就很复杂的对象。
一、生活中的场景:组装一台定制电脑
你去电脑城组装电脑,通常是这样的流程:
- 你 (Client / 客户端):告诉装机员你的需求:“我要一台能打游戏、预算 8000 的电脑”。
- 装机员 (Director / 指挥者):他根据你的需求,制定了一套装配方案:先选主板,再配 CPU,然后是内存、显卡、硬盘、电源... 他指挥着整个构建过程。
- 零件供应商 (Builder / 建造者):装机员可能会给你两套方案,一套是“Intel 酷睿 i7 + Nvida 显卡”的组合,另一套是“AMD 锐龙 R7 + AMD 显卡”的组合。这两个供应商都提供了相同的零件接口(主板、CPU 等),但提供了不同的具体实现。
- 最终的电脑 (Product / 产品):无论选择哪套方案,按照装机员的步骤,最终都得到一台完整的、可以使用的电脑。
在这个过程中,“装配的步骤”(由 Director 控制)和“具体零件的选择与实现”(由 Builder 负责)是分开的。
二、没有建造者模式的代码:混乱的构造器
假设我们要创建一个功能丰富的 Modal (弹窗) 组件。一个弹窗可能有很多配置项:标题、内容、是否有取消按钮、确认按钮的文字、回调函数等等。
方案一:可怕的构造函数参数
javascript
class Modal {
constructor(
title,
content,
showCancelBtn,
confirmText,
cancelText,
onConfirm,
onCancel
) {
this.title = title;
this.content = content;
// ... 大量属性赋值
}
}
// 使用起来像一场噩梦
const myModal = new Modal(
"提示",
"确定要删除吗?",
true,
"是的",
"不了",
() => {
console.log("确认");
},
() => {
console.log("取消");
}
);
// 如果我只想改个标题,并且不需要取消按钮呢?
const anotherModal = new Modal(
"警告",
"操作不可逆!",
false,
null,
null,
myCallback,
null
);
// 大量的 null 占位符,非常容易出错,可读性极差!方案二:巨大的配置对象
这在 JavaScript 中更常见,但也有其局限性。
javascript
class Modal {
constructor(config) {
this.title = config.title || "默认标题";
this.content = config.content || "";
// ...
}
}
// 使用时
const myModal = new Modal({
title: "提示",
content: "确定要删除吗?",
onConfirm: () => {},
});这种方式比方案一好得多,但问题在于:
- 缺乏过程:它是一次性的配置,无法对构建的步骤进行约束和验证。
- 配置复杂:当配置项之间有依赖关系时,这个
config对象会变得非常复杂,难以维护。
三、使用建造者模式重构
建造者模式通过提供链式调用,让对象的创建过程变得像写文章一样清晰。
第 1 步:产品 (Product)
这是我们最终要得到的对象。它就是一个纯粹的数据容器。
javascript
class Modal {
constructor() {
this.title = "";
this.content = "";
this.showCancelBtn = false;
// ... 其他默认值
}
show() {
console.log("--- Modal ---");
console.log("Title:", this.title);
console.log("Content:", this.content);
console.log("Show Cancel:", this.showCancelBtn);
console.log("-------------");
}
}第 2 步:建造者 (Builder)
这是核心。它提供一系列方法来一步步配置 Product,并返回 this 以实现链式调用。
javascript
class ModalBuilder {
constructor() {
// 先创建一个“毛坯房”
this.modal = new Modal();
}
setTitle(title) {
this.modal.title = title;
return this; // 返回 this 是实现链式调用的关键!
}
setContent(content) {
this.modal.content = content;
return this;
}
setShowCancelBtn(show) {
this.modal.showCancelBtn = show;
return this;
}
// 最后,提供一个 build 方法返回最终的产品
build() {
return this.modal;
}
}第 3 步:使用建造者
现在,创建 Modal 的过程变得无比优雅和清晰:
javascript
const alertModal = new ModalBuilder()
.setTitle("系统警报")
.setContent("您的磁盘空间不足!")
.build();
const confirmModal = new ModalBuilder()
.setTitle("操作确认")
.setContent("您确定要永久删除该文件吗?")
.setShowCancelBtn(true)
.build();
alertModal.show();
confirmModal.show();看,代码不仅可读性极强,而且我们再也不用担心参数顺序和 null 占位符了。我们可以按任意顺序调用配置方法,只配置我们关心的部分。
四、与工厂模式的对比
这是初学者最容易混淆的地方。一图胜千言:
| 对比维度 | 工厂模式 (Factory Pattern) | 建造者模式 (Builder Pattern) |
|---|---|---|
| 关注点 | 创建的结果 | 创建的过程 |
| 过程 | 一步到位,直接返回产品 | 分步构建,过程可控 |
| 产品复杂度 | 通常用于创建相对简单的对象 | 专门用于创建复杂的对象 |
| 比喻 | 标准化流水线,一键出货 | DIY 定制装配,精雕细琢 |
简单来说:当你不关心对象是如何创建的,只需要一个结果时,用工厂模式。当你需要精细地控制对象的每一个部分的构建过程时,用建造者模式。
五、优缺点与适用场景
优点
- 可读性高:链式调用让对象的创建过程非常清晰。
- 更好的控制:将复杂的构建逻辑与产品本身分离,可以对构建过程进行更精细的控制。
- 可扩展性好:新增产品部件时,只需在 Builder 中增加新的方法,而无需改动现有代码。
缺点
- 代码量增加:需要额外创建 Builder 类,对于简单对象来说,会显得代码冗余。
- 产品必须有共同点:通常被构建的产品需要有相似的结构或共同的父类。
适用场景
- 当需要创建的对象的属性很多,且有很多是可选的时候。
- 当对象的创建过程本身就是一个复杂的业务逻辑,需要分步骤完成时。
- 当希望同样的构建过程可以创建出不同形态的产品时(例如,
XMLBuilder和JSONBuilder都遵循相同的构建步骤,但产出不同格式)。
创建型模式小结
至此,我们已经学习完了所有核心的创建型模式。它们从不同的角度解决了“如何优雅地创建对象”这一核心问题:
- 工厂模式:隐藏了具体的产品类,由工厂决定生产什么。 -- 单例模式:保证了实例的全局唯一性。
- 原型模式:通过克隆来高效创建对象,尤其适合创建成本高的场景。
- 建造者模式:通过分步构建来创建复杂对象,提供了极佳的可读性和灵活性。
掌握了它们,你就拥有了应对各种对象创建场景的强大“工具箱”。