☝️设计模式-单例

98 阅读5分钟

1. 核心定义

单例模式(Singleton Pattern)是一种确保一个类只有一个实例,并提供一个全局访问点来获取该实例的设计模式。

  • 生活比喻 🏫:就像一所学校只有一个校长,单例模式确保程序中某个特定的类只有一个“分身”。

2. TypeScript 实现三要素

要创建一个单例类,通常需要三个关键部分:

  • private static instance:一个私有的静态变量,用于存储唯一的实例。
  • private constructor():一个私有的构造函数,防止外部通过 new 关键字随意创建新实例。
  • public static getInstance():一个公有的静态方法,作为获取实例的唯一入口。它会检查实例是否存在,若不存在则创建,否则直接返回已有的实例。

3. 优缺点与应用场景

  • 优点 👍
    • 保证实例唯一:有效避免了多个实例造成的逻辑混乱或资源冲突。
    • 节约系统资源:对于创建开销大的对象(如数据库连接),可以复用实例,降低消耗。
    • 提供全局访问点:方便在程序的任何地方访问和管理共享状态。
  • 缺点 👎
    • 对单元测试不友好:全局状态使得测试隔离变得困难。
    • 违反单一职责原则:类既要负责业务逻辑,又要负责管理自己的实例。
    • 隐藏依赖关系:在类内部直接调用单例,使得依赖关系不明确。
  • 适用场景
    • 需要全局唯一的组件,如日志记录器 (Logger)全局配置管理器 (ConfigManager)数据库连接池
  • 不适用场景
    • 与特定用户或会话绑定的对象,如购物车。每个用户都需要一个独立的购物车实例。

4. 两大基本类型

  • 饿汉式 (Eager Initialization)
    • 核心:在类加载时就立即创建实例。
    • 优点:实现简单,天生线程安全。
    • 缺点:若实例从未使用,会造成资源浪费。
  • 懒汉式 (Lazy Initialization)
    • 核心:在第一次调用 getInstance()创建实例(延迟加载)。
    • 优点:按需创建,节约资源。
    • 缺点:在传统多线程语言中需处理复杂的线程安全问题。

5. "加锁" 与线程安全问题 (TypeScript 环境)

  • 同步单例
    • 结论不需要加锁
    • 原因:JavaScript 的单线程事件循环机制保证了代码在执行 if...new 检查和创建的过程中不会被中断,天然避免了竞争问题。
  • 异步单例
    • 结论需要一种“锁”机制
    • 场景:当单例的初始化包含 async/await 操作时(如读取文件、网络请求)。
    • 实现:最佳实践是使用一个共享的 Promise 变量充当“锁”,确保初始化逻辑只被完整执行一次。

一句话总结 💡:在 TypeScript 中,同步单例很简单;异步单例则必须用 Promise 来为初始化过程“加锁”。

 

// 饿汉模式
class EagerSingleton {
    // 1. 关键:在类加载时就直接创建实例,并设为 readonly 防止被修改
    private static readonly instance: EagerSingleton = new EagerSingleton();

    // 2. 将构造函数设为私有,防止外部通过 `new` 创建
    private constructor() {
        console.log("饿汉式单例:实例在类加载时已创建。🚀");
    }

    // 3. 提供一个公有的静态方法,直接返回已经创建好的实例
    public static getInstance(): EagerSingleton {
        return EagerSingleton.instance;
    }

    // 示例方法
    public showMessage(): void {
        console.log("这是一个来自饿汉式单例的消息。");
    }
}

// --- 使用方法 ---
console.log("--- 开始使用饿汉式单例 ---");
const eager1 = EagerSingleton.getInstance();
const eager2 = EagerSingleton.getInstance();
eager1.showMessage();

// 验证是否是同一个实例
console.log("eager1 === eager2:", eager1 === eager2); // true
console.log("--- 饿汉式单例使用结束 ---");

JavaScript

 

//懒汉模式

class LazySingleton {
    // 1. 关键:声明一个静态实例,但不立即初始化,默认为 null
    private static instance: LazySingleton | null = null;

    // 2. 将构造函数设为私有
    private constructor() {
        console.log("懒汉式单例:实例在首次调用 getInstance() 时创建。🤔");
    }

    // 3. 提供公有静态方法,并在其中检查实例是否存在
    public static getInstance(): LazySingleton {
        // 如果实例不存在,才进行创建
        if (!LazySingleton.instance) {
            LazySingleton.instance = new LazySingleton();
        }
        // 返回实例
        return LazySingleton.instance;
    }

    // 示例方法
    public showMessage(): void {
        console.log("这是一个来自懒汉式单例的消息。");
    }
}

// --- 使用方法 ---
console.log("\n--- 开始使用懒汉式单例 ---");
console.log("准备第一次获取实例...");
const lazy1 = LazySingleton.getInstance(); // 此时才会打印构造函数中的消息

console.log("准备第二次获取实例...");
const lazy2 = LazySingleton.getInstance(); // 不会再打印构造函数中的消息
lazy1.showMessage();

// 验证是否是同一个实例
console.log("lazy1 === lazy2:", lazy1 === lazy2); // true
console.log("--- 懒汉式单例使用结束 ---");

JavaScript

 

// 带锁懒汉模式 
class AsyncSafeSingleton {
    private static instance: AsyncSafeSingleton | null = null;
    
    // 这就是我们的“锁”:一个代表初始化过程的 Promise
    private static initializingPromise: Promise<AsyncSafeSingleton> | null = null;

    private constructor() {
        console.log("异步安全单例:开始初始化... (只会执行一次)");
    }
    
    // 模拟一个异步的初始化过程
    private async initialize(): Promise<void> {
        // 假设这里有 await db.connect(), await fs.readFile() 等
        await new Promise(resolve => setTimeout(resolve, 1000)); 
    }

    public static getInstance(): Promise<AsyncSafeSingleton> {
        // 如果实例已存在,直接返回
        if (this.instance) {
            return Promise.resolve(this.instance);
        }

        // 如果正在初始化(“锁”被占用),则等待它完成
        if (this.initializingPromise) {
            return this.initializingPromise;
        }

        // 第一个调用者,创建“锁”并开始初始化
        this.initializingPromise = (async () => {
            const instance = new AsyncSafeSingleton();
            await instance.initialize(); // 执行异步初始化
            this.instance = instance; // 将实例赋值
            this.initializingPromise = null; // 释放“锁”
            console.log("异步安全单例:初始化完成!");
            return instance;
        })();

        return this.initializingPromise;
    }
}

// --- 使用方法 ---
console.log("并发调用 getInstance:");
AsyncSafeSingleton.getInstance().then(s => console.log("第一次调用完成"));
AsyncSafeSingleton.getInstance().then(s => console.log("第二次调用完成"));
AsyncSafeSingleton.getInstance().then(s => console.log("第三次调用完成"));