文章

创建型模式深度解析

前端最常用的三种创建型模式:单例、工厂、建造者,以及它们在现代框架和业务中的真实落点。

创建型模式深度解析

一句话概括

创建型模式解决的是”对象怎么被创建出来“的问题——把对象的创建逻辑从使用逻辑中剥离,让代码不关心具体怎么 new、new 的是哪个类。前端最常用的三个是:单例模式(全局唯一实例,如全局 store、日志器)、工厂模式(把”创建哪个子类”的决策集中起来,如组件注册、插件创建)、建造者模式(分步构建复杂对象,如链式配置、复杂表单数据)。它们的共同价值是解耦”创建”与”使用”,让代码好扩展、好测试。

核心知识点

1. 单例模式:全局唯一,且 ES Module 就是天然的

单例保证一个类全局只有一个实例。传统实现靠闭包缓存,但 ES Module 的模块级缓存已经天然提供了单例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ❌ 老式实现:手动闭包 + 静态方法
class Logger {
  static #instance = null;
  static getInstance() {
    if (!Logger.#instance) Logger.#instance = new Logger();
    return Logger.#instance;
  }
}

// ✅ ES Module 天然单例:模块只执行一次,导出同一个引用
// logger.js
class Logger { log(msg) { console.log(msg); } }
export const logger = new Logger(); // 无论被 import 多少次,都是同一个实例

// store.js
export const store = createStore(reducer); // Redux/Zustand 的 store 就是模块级单例

要点:模块导出即单例,这是现代前端最自然的写法。但单例的代价是隐式共享状态 + 难测试(测试之间会串数据),所以单例更适合”无状态的服务”(日志、工具),有状态的要谨慎,最好暴露工厂函数或可重置接口。

2. 工厂模式:把”创建哪个”的决策集中

工厂的核心是用一个函数封装创建逻辑,调用方只传参数,不关心返回的具体类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 简单工厂:根据 type 返回不同实例
function createShape(type) {
  switch (type) {
    case 'circle': return new Circle();
    case 'rect':   return new Rect();
    default: throw new Error('未知类型: ' + type);
  }
}

// 前端真实落点:根据配置动态创建组件/插件
function createChart(type, options) {
  const charts = { line: LineChart, bar: BarChart, pie: PieChart };
  const Chart = charts[type];
  return Chart ? new Chart(options) : null;
}

好处:新增一种类型只需在工厂里加一个 case,调用方零改动。这正是”开闭原则”(对扩展开放、对修改封闭)的体现。

3. 工厂方法 vs 抽象工厂

两者常被混淆,区别在于抽象层级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 工厂方法:每个产品一个工厂方法,子类决定具体产品
class ButtonFactory {
  createButton() { throw new Error('子类实现'); }
}
class IosButtonFactory extends ButtonFactory {
  createButton() { return new IosButton(); }  // iOS 风格的按钮
}
class AndroidButtonFactory extends ButtonFactory {
  createButton() { return new AndroidButton(); }
}

// 抽象工厂:一个工厂生产"一族"相关产品
class UIFactory {
  createButton() {}
  createDialog() {}
}
// 每个具体工厂负责一整套(按钮+弹窗)风格统一

一句话记:工厂方法 = 一个产品,子类决定哪个;抽象工厂 = 一族产品,保证风格配套。抽象工厂天然适合”跨平台 UI 主题”这种”换一套就是换一族”的场景。

4. 建造者模式:分步构建复杂对象

当一个对象构造参数巨多、还带可选配置,直接 new 会变成”参数地狱”。建造者用链式分步让构建过程清晰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class RequestBuilder {
  #config = { method: 'GET', headers: {}, timeout: 5000 };
  method(m)   { this.#config.method = m; return this; }
  url(u)      { this.#config.url = u; return this; }
  timeout(t)  { this.#config.timeout = t; return this; }
  header(k, v){ this.#config.headers[k] = v; return this; }
  build()     { return this.#config; } // 最后一步产出成品
}

// 使用:每一步都语义清晰,可任意组合
const req = new RequestBuilder()
  .method('POST')
  .url('/api/users')
  .header('Content-Type', 'application/json')
  .timeout(3000)
  .build();

它和工厂的区别:工厂一步到位返回对象,建造者分多步、按需组合。适合”参数多、有默认值、可选组合”的构建场景。前端里 axios 的配置对象、ORM 的 query builder 都是这个思想。

5. 创建型模式与依赖注入(DI)的关系

面试高频追问。要点:工厂是 DI 的底层实现手段——DI 容器本质就是一个”增强版工厂”,它负责按依赖关系自动创建并注入对象:

1
2
3
4
5
6
7
8
9
// 手写 DI:容器用工厂逻辑创建并注入依赖
class Container {
  #deps = new Map();
  register(key, factory) { this.#deps.set(key, factory); }
  resolve(key) {
    const factory = this.#deps.get(key);
    return factory(this); // 把容器传进去,支持递归解析依赖
  }
}

区别在控制权:工厂是”调用方主动要”,DI 是”容器主动给”。理解这点,就能回答”前端框架(Angular、Nest)的 DI 是怎么用工厂思想实现的”。

其实你每天都在用

  • Redux/Zustand 的 store:全局唯一实例,模块导出即单例,所有组件共享同一份状态
  • 组件库的按需创建:createChart('pie')、createIcon('search'),根据名字返回不同组件实例,就是工厂
  • 链式查询构建:ORM 里 User.query().where('age', '>', 18).orderBy('name').limit(10),每一步返回 this 分步构建,就是建造者
  • 跨平台 UI 主题:iOS 一套按钮+弹窗风格、Android 一套,换一个 UIFactory 全换,就是抽象工厂
  • axios 的请求配置对象:{ method, url, headers, timeout } 一堆可选参数组合成一个请求,本质是建造者思想

常见误解(FAQ)

❌ 误区一:”单例模式好,全局共享状态方便,尽量多用”

单例是隐式的全局变量,它带来的是隐式耦合 + 难测试:所有用到它的地方都悄悄依赖同一个状态,测试之间会串数据,重构时不知道谁改了这个状态。前端里模块导出单例要分清”无状态服务”(日志、工具,安全)和”有状态数据”(业务 store,要谨慎并提供重置手段)。滥用单例是”省事一时、埋雷一世”。

❌ 误区二:”简单工厂和工厂方法只是叫法不同”

本质区别:简单工厂用 if/switch 在一个函数里决定所有类型(新增类型要改工厂本身),工厂方法把”决定创建哪个”下放到子类(新增类型只需新增子类,工厂本身不动)。简单工厂违反开闭原则,工厂方法遵守。抽象工厂则是更高一层的”一族产品”的工厂。

❌ 误区三:”建造者模式和工厂模式能互换”

工厂是”一步到位“——传入参数,直接返回成品,适合简单对象。建造者是”分步构建“——多个步骤按需组合,适合参数多、有默认值、可选组合的复杂对象。用工厂造”几十个可选参数”的对象会变成参数地狱;用建造者造”两三个字段”的简单对象又过度设计。选型看构建复杂度。

❌ 误区四:”设计模式是后端的东西,前端用不上”

创建型模式在前端处处可见:全局 store 是单例、组件注册表是工厂、ORM/请求配置是建造者、框架 DI 是工厂的增强。前端不强调”背模式名”,但每天都在用这些思想。理解它们,能帮你读懂框架源码、写出更解耦的业务代码,而不是死记硬背 UML 图。

一句话总结

创建型模式的核心不是”怎么 new 对象”的技巧,而是把”创建”和”使用”分开——单例管唯一、工厂管选型、建造者管分步,三者共同让代码从”写死创建逻辑”走向”按需扩展、开闭有度”。

本文由作者按照 CC BY 4.0 进行授权