使用 Typescript 实现单例模式

本篇文章主要介绍使用 Typescript 实现「单例模式」

定义

单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。
在应用这个模式时,单例对象的类必须保证只有一个实例存在。
许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。

优点

  • 延迟初始化。我们不需要在整个游戏周期开始前创建实例,而是在第一次使用时创建。因此,这样实现的单例可以节省 CPU 周期和内存使用。
  • 获取初始化后的数据。当游戏初始化完毕后,再调用单例,就可以在单例初始化的时候,获取到我们游戏初始化的数据,这样就可以更好的控制游戏的行为。
  • 可继承的单例。单例模式可以继承,这样就可以在单例子类中添加新的方法,而不会影响到父类的单例。

缺点

  • 全局可访问。由于单例都是全局变量,如果大量的代码使用了这个单例,而其中一个出了问题,我们必须从数百个引用中去寻找出错的那个。
  • 增加耦合。如果我们的单例类和其他类之间有很多依赖关系,那么这些类会变得庞大。
  • 创建实例的限制。由于单例模式的特点,我们只能创建一个单例实例,而不能创建多个实例。这就导致当要扩展单例时,我们只能在单例类中添加新的方法,而不能使用多个实例来扩展单例。
  • 延迟初始化的副作用。当在第一次使用时初始化单例,如果当前 CPU 和内存占用较高,单例初始化可能会导致游戏卡顿。

改进方案

  • 尽可能少的使用单例
  • 限制全局访问。确保只有一个实例,但它不能被外部访问。这就解决了那些 “多实例” 所带来的问题,同时又不暴露全局入口来削弱整体的框架。
  • 使用 “服务定位器”。简而言之,将这些 “杂类” 比如说 “日志系统”、“存档系统” 之类的类改为 “非单例模式”,之后将他们存储在一个全局对象中,以便其他类通过 “全局对象->子系统” 来调用。这将有效地减少对单实例的使用。

实现

饿汉式单例

无论你是否使用,在类加载的时候就初始化了,所以会占据空间,浪费内存。

饿汉式-导出实例

1
2
3
4
5
6
7
8
class Singleton {
public print(){
console.log('hello world!');
}
}
export const singleton = new Singleton();

singleton.print(); // hello world!

饿汉式-静态类

1
2
3
4
5
6
7
export default class Singleton {
public static print(){
console.log('hello world!');
}
}

Singleton.print(); // hello world!

饿汉式-静态属性

1
2
3
4
5
6
7
8
9
10
11
12
export default class Singleton {
private static _instance: Singleton = new Singleton();
public static getInstance(): Singleton {
return Singleton._instance;
}

public print(){
console.log('hello world!');
}
}

Singleton.getInstance().print(); // hello world!

懒汉式

在需要的时候进行实例化,相对来说不浪费内存。

懒汉式-静态属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default class Singleton {
private static _instance: Singleton;
public static getInstance(): Singleton {
if (!Singleton._instance) {
Singleton._instance = new Singleton();
}
return Singleton._instance;
}

public print(){
console.log('hello world!');
}
}

Singleton.getInstance().print(); // hello world!

懒汉式-静态 getter 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default class Singleton {
private static _instance: Singleton;
public static get instance(): Singleton {
if (!Singleton._instance) {
Singleton._instance = new Singleton();
}
return Singleton._instance;
}

public print(){
console.log('hello world!');
}
}

Singleton.instance.print(); // hello world!

可继承的单例

如果经常写单例,可以发现以上这些实现方式,都无法让单例类可以被继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class SingletonA {
private static _instance: SingletonA;
public static getInstance(): SingletonA {
if (!SingletonA._instance) {
SingletonA._instance = new SingletonA();
}
return SingletonA._instance;
}

public print(){
console.log('this is SingletonA!');
}
}

class SingletonB extends SingletonA {
public print(){
console.log('this is SingletonB!');
}
}

SingletonB.getInstance().print(); // this is SingletonA!
console.log(SingletonB.getInstance() instanceof SingletonA); // true
console.log(SingletonB.getInstance() instanceof SingletonB); // false

如上例所示,在 SingletonB 继承 SingletonA 后,通过 getInstance 静态方法获取到的并非 SingletonB 的实例,而是 SingletonA 的实例,并且调用的 print 方法也是没有复写的。

那么有没有什么方法可以实现单例的继承呢,那就要用到 Typescript 的范型功能了。
可以看到在 getInstance 静态方法我们创建和返回的是指定的 SingletonA 类型,如果通过范型将类的定义穿参进去,指定我们要创建和返回的类型,那就实现我们要的继承单例了。
以下是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export class SingletonA {
protected static _instance: SingletonA;

public static getInstance<T extends typeof SingletonA>(this: T): InstanceType<T> {
if (!this._instance) {
this._instance = new this();
}
return this._instance as InstanceType<T>;

}
public print(){
console.log('this is SingletonA!');
}
}

export class SingletonB extends SingletonA {
public print(){
console.log('this is SingletonB!');
}
}

SingletonB.getInstance().print(); // this is SingletonB!
console.log(SingletonB.getInstance() instanceof SingletonA); // true
console.log(SingletonB.getInstance() instanceof SingletonB); // true

细心的朋友可能注意到,我们现在使用 getInstance 静态方法,通过传入范型对象的方式,将当前类定义初始化为实例。
那么是否可以使用静态 getter 属性实现单例的继承呢?
经过尝试,我们发现静态 getter 属性并不能传递参数,这意味着我们不能直接向静态 getter 属性添加范型参数实例化。

范型对象是通过方法进行传参的,那么我们如果能够通过方法进行类的定义化,是不是就能够进行单例的继承了呢? 我们尝试过后发现,通过方法返回类定义的话,是可以实现继承的! 以下是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export function SingletonA<T>() {
return class C {
protected static _instance: unknown = null;
public static get instance(): T {
if (C._instance === null) {
C._instance = new this();
}
return C._instance as T;
}

public print(){
console.log('this is SingletonA!');
}
}
}

export function SingletonB<T>(){
return class C extends SingletonA<C & T>() {
public print(){
console.log('this is SingletonB!');
}
}
}

export class BaseManager extends SingletonB<BaseManager>(){ }

BaseManager.instance.print(); // this is SingletonB!
console.log(BaseManager.instance instanceof BaseManager); // true

参考

本文作者:雪糕
本文地址: https://blooddot.cool/posts/d3b10281/
版权声明:转载请注明出处!