Skip to content

3.2 建造者模式 (Builder Pattern)

如果说工厂模式是“一键生产”,原型模式是“一键克隆”,那么建造者模式就是“精工细作的自定义装配”。

核心定义:将一个复杂对象的构建过程与其最终表示相分离,使得同样的构建过程可以创建出不同的表示。

这个模式主要用于创建那些构造过程本身就很复杂的对象。

一、生活中的场景:组装一台定制电脑

你去电脑城组装电脑,通常是这样的流程:

  1. 你 (Client / 客户端):告诉装机员你的需求:“我要一台能打游戏、预算 8000 的电脑”。
  2. 装机员 (Director / 指挥者):他根据你的需求,制定了一套装配方案:先选主板,再配 CPU,然后是内存、显卡、硬盘、电源... 他指挥着整个构建过程。
  3. 零件供应商 (Builder / 建造者):装机员可能会给你两套方案,一套是“Intel 酷睿 i7 + Nvida 显卡”的组合,另一套是“AMD 锐龙 R7 + AMD 显卡”的组合。这两个供应商都提供了相同的零件接口(主板、CPU 等),但提供了不同的具体实现。
  4. 最终的电脑 (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 定制装配,精雕细琢

简单来说:当你不关心对象是如何创建的,只需要一个结果时,用工厂模式。当你需要精细地控制对象的每一个部分的构建过程时,用建造者模式

五、优缺点与适用场景

优点

  1. 可读性高:链式调用让对象的创建过程非常清晰。
  2. 更好的控制:将复杂的构建逻辑与产品本身分离,可以对构建过程进行更精细的控制。
  3. 可扩展性好:新增产品部件时,只需在 Builder 中增加新的方法,而无需改动现有代码。

缺点

  1. 代码量增加:需要额外创建 Builder 类,对于简单对象来说,会显得代码冗余。
  2. 产品必须有共同点:通常被构建的产品需要有相似的结构或共同的父类。

适用场景

  • 当需要创建的对象的属性很多,且有很多是可选的时候。
  • 当对象的创建过程本身就是一个复杂的业务逻辑,需要分步骤完成时。
  • 当希望同样的构建过程可以创建出不同形态的产品时(例如,XMLBuilderJSONBuilder 都遵循相同的构建步骤,但产出不同格式)。

创建型模式小结

至此,我们已经学习完了所有核心的创建型模式。它们从不同的角度解决了“如何优雅地创建对象”这一核心问题:

  • 工厂模式:隐藏了具体的产品类,由工厂决定生产什么。 -- 单例模式:保证了实例的全局唯一性。
  • 原型模式:通过克隆来高效创建对象,尤其适合创建成本高的场景。
  • 建造者模式:通过分步构建来创建复杂对象,提供了极佳的可读性和灵活性。

掌握了它们,你就拥有了应对各种对象创建场景的强大“工具箱”。