本篇文章主要介绍使用 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();
|
饿汉式-静态类
1 2 3 4 5 6 7
| export default class Singleton { public static print(){ console.log('hello world!'); } }
Singleton.print();
|
饿汉式-静态属性
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();
|
懒汉式
在需要的时候进行实例化,相对来说不浪费内存。
懒汉式-静态属性
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();
|
懒汉式-静态 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();
|
可继承的单例
如果经常写单例,可以发现以上这些实现方式,都无法让单例类可以被继承。
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(); console.log(SingletonB.getInstance() instanceof SingletonA); console.log(SingletonB.getInstance() instanceof SingletonB);
|
如上例所示,在 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(); console.log(SingletonB.getInstance() instanceof SingletonA); console.log(SingletonB.getInstance() instanceof SingletonB);
|
细心的朋友可能注意到,我们现在使用 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(); console.log(BaseManager.instance instanceof BaseManager);
|
参考
本文作者:雪糕
本文地址: https://blooddot.cool/posts/d3b10281/
版权声明:转载请注明出处!