单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
单例模式是创建型设计模式的一种,它的实现方式有很多种,尤其像在后端语言(如java)中,会涉及到多线程、多进程实例唯一的情况,就需要考虑到加锁等操作。而对于前端JavaScript而言,其本身就是执行在单线程上的,所以实现起来就相对简单一些。常见的一些实现方式:
单例的实现
1. 简单单列模式实现
如下代码(typescript),通过创建单例类Singleton实现单例模式,并满足如下条件
- 声明静态属性存储单例,这里不能使用非静态属性,原因是返回实例的接口方法
getInstance是静态方法 - 私有的构造函数,阻止外部通过new的方式创建
Singleton的实例 - 暴露返回单例的静态方法
getInstance
/**
* 简单单例模式
*/
class Singleton {
// 1. 静态属性保存实例
private static instance: Singleton;
public name: string = '';
// 2. 私有构造函数,阻止被实例化
private constructor(name: string) {
this.name = name;
}
public getName() {
return this.name;
}
// 3. 获取单例的接口
public static getInstance(name: string): Singleton {
if (Singleton.instance === undefined) {
Singleton.instance = new Singleton(name);
}
return Singleton.instance;
}
}
// test ====================================
const ins1 = Singleton.getInstance('hello');
const ins2 = Singleton.getInstance('world');
console.log('ins1 === ins2 ?', ins1 === ins2); // ins1 === ins2 ? true
console.log(ins1.name, ins2.getName()); // hello hello
2. 通用单例模式实现
通用单例模式,更像是一个单例工厂,它可通过参数方式,将需要创建单例的类传入工厂,工厂负责返回一个获取该类单例的方法。代码实现也比较简单
/**
* 通用单例模式实现
*/
function getInstance<T>(Cls: new (...args: any[]) => T) {
let instance: T;
return function(...args: any[]) {
if (instance === undefined) {
instance = new Cls(...args);
}
return instance;
};
}
// test ====================================
class Foo {
public name: string;
constructor(name: string) {
this.name = name;
}
public getName() {
return this.name;
}
}
const getFooInstance = getInstance(Foo);
const ins3 = getFooInstance('world');
const ins4 = getFooInstance('hello');
console.log('ins3 === ins4 ?', ins3 === ins4); // ins3 === ins4 ? true
console.log(ins3.name, ins4.getName()); // world world
通过以上代码可以看出,getInstance 函数接收一个类作为参数,内部以闭包的方式保留类的引用,并随匿名函数返回。当再次执行返回的函数时,即可得到该类的唯一实例,并可传入一些初始化参数(只有第一次的参数生效)。
单例的用处
从业务概念上,有些状态、数据、组件等在系统中只应该保存一份,就比较适合设计为单例类。比如,全局的配置信息,全局用户信息、全局弹窗组件等。
单例的优缺点
优点:
- 划分独立命名空间,避免全局变量污染
- 代码逻辑集中,业务高度内聚
- 只创建一个实例,减少对象频繁创建、销毁的消耗
缺点:
- 对 OOP 特性的支持不友好(单例模式内部自己创建实例,外部无法通过new来实例化,无法继承扩展)
- 对代码的可测试性不友好(由于单例模式属硬编码的方式,会导致内部的接口、数据或依赖都无法很好模拟)