一、前言
本文将聚焦于其中两种最具代表性的创建型模式:单例模式(Singleton Pattern) 与 工厂模式(Factory Pattern)
- 单例模式关注“如何确保系统中某个类只有一个实例”
- 工厂模式则回答“如何将对象的创建过程抽象并解耦”
二、单例模式(Singleton Pattern)
2.1 模式简介:唯一,不可复制的存在
在整个设计模式体系中,单例模式的地位可谓“既基础又危险”。其核心思想极为简洁:一个类,在其生命周期中,只能产生一个实例,且这个实例应对全局共享、可访问
抽象表达如下图所示:
+---------------------+
| Singleton |
|---------------------|
| - instance |
| + getInstance() |
+---------------------+
调用 getInstance() 方法时,无论多少次,总返回同一个对象。这样,我们既规避了资源浪费,也避免了状态冲突
2.2 JavaScript 中的几种实现方式
2.2.1 对象字面量法:最直白的写法
const Singleton = {
name: "唯一体",
getName() {
return this.name;
}
};
毫无疑问,这种方式在语义上已经形成“唯一实例”,但它更接近于静态对象而非真正的“懒加载单例”
2.2.2 闭包懒汉式:真正意义的“第一次调用才生成”
const Singleton = (function () {
let instance;
function createInstance() {
return {
timestamp: Date.now()
};
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
这一实现的高妙之处在于:将“实例的生命周期”托付给了闭包,而不是全局变量。调用 Singleton.getInstance() 若干次,均只会返回同一个对象
2.2.3 ES6 class + static 属性
class Singleton {
constructor() {
if (Singleton.instance) return Singleton.instance;
this.timestamp = Date.now();
Singleton.instance = this;
}
}
const a = new Singleton();
const b = new Singleton();
console.log(a === b); // true
这种方式将“是否已创建”的逻辑融入类本身,虽然语法糖增多,但其语义更加贴近面向对象设计
2.2.4 ES6 模块:天然的单例结构
// singleton.js
let counter = 0;
export function increase() {
counter++;
return counter;
}
在 ES Module 规范下,模块在第一次 import 时就被执行并缓存,后续 import 会复用之前的模块实例,因此天生具备单例特性
2.3 单例模式的优缺点
| 优点 | 缺点 |
|---|---|
| 避免资源浪费 | 测试困难(全局状态依赖) |
| 控制全局状态 | 模块间耦合增强 |
| 简化对象管理逻辑 | 多线程(非 JS 环境)下需注意线程安全 |
三、工厂模式(Factory Pattern)
3.1 模式简介:把“new”藏起来
若说单例是“控制实例数量”,那工厂模式则是“抽象实例创建”。
其主旨在于:让对象的使用者无需知道其具体构造逻辑,只需告诉工厂:我需要什么,它自然会交付你一份成品。这是一种“解耦创造与使用”的优雅思想
图示如下:
Client ---> Factory ---> Product
在 JavaScript 中,我们常用函数或类来封装这种工厂逻辑
3.2 JavaScript 中的实现方式
3.2.1 简单工厂模式:用参数控制实例类型
function createShape(type) {
switch (type) {
case "circle":
return { draw: () => console.log("O") };
case "square":
return { draw: () => console.log("◼") };
default:
throw new Error("Unknown shape type.");
}
}
const shape = createShape("circle");
shape.draw();
此为工厂模式的最基础雏形,虽简单,却已实现了“选择 → 实例 → 隐藏逻辑”的闭环
3.2.2 工厂方法模式:父类定义工厂方法,子类决定产品类型
class ShapeFactory {
createShape() {
throw new Error("Abstract method");
}
}
class CircleFactory extends ShapeFactory {
createShape() {
return { draw: () => console.log("⚪") };
}
}
const factory = new CircleFactory();
factory.createShape().draw(); // ⚪
3.2.3 抽象工厂模式:多产品族统一构造
function createUIFactory(theme) {
if (theme === "dark") {
return {
createButton: () => ({ style: "dark", text: "Button" }),
createInput: () => ({ style: "dark", placeholder: "Input" })
};
}
if (theme === "light") {
return {
createButton: () => ({ style: "light", text: "Button" }),
createInput: () => ({ style: "light", placeholder: "Input" })
};
}
}
这种模式适用于需要批量创建风格一致的一组对象时,提升代码一致性与解耦度
3.3 工厂模式的优缺点
| 优点 | 缺点 |
|---|---|
| 将“构造细节”与“使用逻辑”解耦 | 类或函数数量激增,结构变复杂 |
| 易于扩展新类型 | 不同产品间交互可能带来维护负担 |
| 代码可读性与一致性增强 | 初学者理解成本相对较高 |
你提的总结非常精准,完全可以作为全文的收尾归纳部分,用于强调两个模式的适用边界与设计意图。我可以根据你提供的内容,在语言节奏、逻辑层次和专业性表达上做进一步优化和提升。下面是优化后的最终总结段落:
四、总结
在设计模式的世界里,没有绝对的优劣,只有恰当与否。单例与工厂模式,分别代表了两种常见而本质不同的设计需求:控制唯一性 与 封装复杂性
-
单例模式,擅长于构建那些在整个系统中必须且只能存在一个实例的对象。例如:
- 全局配置中心(Configuration)
- 数据库连接池(Connection Pool)
- 日志记录器(Logger)
- 事件总线或消息调度器(Event Bus)
这些对象通常需要集中控制、统一访问,且其生命周期应受到严格管理
-
工厂模式,则更适用于构建逻辑复杂、构造路径多变的对象体系。当你遇到以下场景时,应首先考虑工厂模式:
- 根据参数动态创建不同类型对象(例如根据 JSON 创建 UI 组件)
- 对象创建逻辑复杂或频繁变动(构造依赖外部状态)
- 子系统中需要统一接口但多种实现并存(策略模式结合)
- 需要屏蔽具体实现类,暴露统一 API 给调用者