深入理解 JavaScript 中的单例模式与工厂模式

105 阅读5分钟

一、前言

本文将聚焦于其中两种最具代表性的创建型模式:单例模式(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 给调用者