Skip to content

单例模式 (Singleton pattern)

单例模式介绍

单例模式是一种创建型设计模式,确保某个类只有一个实例,并提供全局访问点.

这种模式适用于当需要确保系统中的特定类只有一个实例存在,且需要在整个应用中全局访问该实例时.单例模式的核心思想是通过私有化构造函数以及全局访问方法,控制实例的创建和访问.

要实现单例模式,常常需要考虑以下几点:

  1. 私有化构造函数:通过将类的构造函数设置为私有,可以防止外部直接实例化对象.
  2. 静态方法获取实例:提供一个静态方法来获取单例对象,该方法负责确保只创建一个实例并返回该实例.
  3. 懒加载:延迟对象的实例化,只有在首次调用时才创建对象.
  4. 线程安全:如果在多线程环境下使用单例模式,需要确保线程安全,避免出现多个实例同时创建的问题.

单例模式在许多场景下都有广泛应用,例如数据库连接池、线程池、配置信息对象等.它可以减少系统中的内存占用、节省资源、提高程序性能,并且方便对实例进行管理和维护.

但在实际应用中需要注意线程安全、懒加载等问题,以确保单例模式的有效性.

基础实现

js
class Singleton {
  constructor(name) {
    this.name = name;
  }
}

Singleton.getInstance = (() => {
  // 通过自执行函数和闭包实现单例模式
  let instance;
  return (...args) => {
    if (!instance) {
      instance = new Singleton(...args); // 缓存
    }
    return instance;
  };
})();

// 约定调用方式如下:
const instance1 = Singleton.getInstance("111");
const instance2 = Singleton.getInstance("222");
console.log(instance1 === instance2, instance1.name);
// 输出:true 111, instance1 和 instance2 是同一个实例
//但是如果这样调用:
const instance3 = new Singleton("333");
console.log(instance1 === instance3, instance3.name);
// 输出:false 333, instance1 和 instance3 不是同一个实例
export default Singleton;

进阶版

解决了 new 调用的问题

js
class Singleton {
  constructor(name) {
    // 判断是否已经有实例
    this.name = name;
    if (typeof Singleton.instance === "object") {
      // 开发环境下可以打印警告
      if (process.env.NODE_ENV === "development") console.warn("已经有实例了");
      return Singleton.instance;
    }
    Singleton.instance = this; // 缓存
    return this;
  }
  static getInstance = (() => {
    // 通过自执行函数和闭包实现单例模式
    let instance;
    return (...args) => {
      if (!instance) {
        Singleton.instance = new Singleton(...args);
        instance = Singleton.instance; // 缓存
      }
      return instance;
    };
  })();
}

const instance1 = new Singleton("111");
const instance2 = new Singleton("222");
const instance3 = Singleton.getInstance("333");
console.log(instance1 === instance2, instance1.name);
// 输出:true 111, instance1 和 instance2 是同一个实例
console.log(instance1 === instance3, instance2.name);
// 输出:true 111, instance1 和 instance3 是同一个实例
console.log(instance2 === instance3, instance3.name);
// 输出:true 111, instance2 和 instance3 是同一个实例

export default Singleton;

高级版

在原有基础上不需要单独在 class 中处理 而是通过代理实现

js
class MyClass {
  constructor(name) {
    this.name = name;
  }
}

const singleton = (className) => {
  // 通过闭包实现单例模式
  let instance;
  // 通过 Proxy 代理构造函数,实现单例模式
  const proxy = new Proxy(className, {
    construct(target, args) {
      // 判断是否已经有实例
      if (!instance) {
        instance = new target(...args);
      } else {
        // 开发环境下可以打印警告
        process.env.NODE_ENV === "development" && console.warn("已存在实例");
      }
      return instance;
    },
  });
  // 更改原型指向 使得 通过实例的constructor属性 仍然指向 代理
  className.prototype.constructor = proxy;
  return proxy;
};

const SingletonMyClass = singleton(MyClass);

const instance1 = new SingletonMyClass("111");
const instance2 = new SingletonMyClass("222");
const instance3 = new instance1.constructor("333");

console.log(instance1 === instance2, instance1.name);
// 输出:true 111, instance1 和 instance2 是同一个实例
console.log(instance1 === instance3, instance3.name);
// 输出:true 111, instance1 和 instance3 是同一个实例
console.log(instance2 === instance3, instance3.name);
// 输出:true 111, instance2 和 instance3 是同一个实例
export default SingletonMyClass;

模块化抽离使用

还可以将 singleton 方法抽离出来 模块化使用

模块化抽离使用 点击展开

模块化

javascript
const singleton = (className) => {
  let instance;
  const proxy = new Proxy(className, {
    construct(target, args) {
      if (!instance) {
        instance = new target(...args);
      } else {
        process.env.NODE_ENV === "development" && console.warn("已存在实例");
      }
      return instance;
    },
  });
  className.prototype.constructor = proxy;
  return proxy;
};
export default singleton;

使用

javascript
import singleton from "singleton.js";

class MyClass {
  constructor(name) {
    this.name = name;
  }
}

const SingletonMyClass = singleton(MyClass);

export default SingletonMyClass;