实现一个ts单例模式基类(支持代码提示、禁止二次实例化)

另一种实现方式的传送门:【TS】另一种实现typescript单例模式的方式(支持代码提示,禁止二次实例化) - 简书 (jianshu.com)

先贴出我的代码,后面再讲讲为什么要这么做:

class Singleton {
    // 实例
    private static _instance: Singleton;
    // 是否是通过getInstance实例化
    private static _instantiateByGetInstance: boolean = false;
    
    /**
     * 获取实例
     */
    public static getInstance<T extends Singleton>(this: (new () => T) | typeof Singleton): T {
        const _class = this as typeof Singleton;
        if (!_class._instance) {
            _class._instantiateByGetInstance = true;
            _class._instance = new _class();
            _class._instantiateByGetInstance = false;
        }
        return _class._instance as T;
    }
    
    /**
     * 构造函数
     * @protected
     */
    protected constructor() {
        if (!(this.constructor as typeof Singleton)._instantiateByGetInstance) {
            throw new Error("Singleton class can't be instantiated more than once.");
        }
    }
}

我在网上搜索了一遍,大多数文章里的ts单例模式或多或少是不够完美的,我的单例模式有以下几个优点:

  1. 子类可以直接继承此Singleton类,无需其他多余代码,比如
class TestClass extends Singleton {
    
}
  1. 支持IDE代码提示
class TestClass extends Singleton {
    testFunc() {
        
    }
}
代码提示
  1. 禁止使用new关键字实例化


    禁止实例化

接下来讲讲为什么要这么写

  • getInstance有泛型参数T,它需要继承Singleton,使用的时候不需要添加泛型参数,当然加上也没问题,比如:TestClass.getInstance<TestClass>()
  • ts函数参数列表中的第一项可以为this。这个this只是用于手动告诉ts编译器它的类型,实际并不需要传入。
  • this参数的类型为(new () => T) | typeof Singleton联合类型。new () => T表示某个构造函数类型,由于TestClass继承自Singleton,同时getInstance是静态方法,所以这里的new () => T代表的是TestClass类。使用了new () => T,就能告诉编辑器当前使用了哪个类,但是这样一来构造函数使用protected或者private修饰时会在编译阶段报错,可能会提示Cannot assign a 'protected' constructor type to a 'public' constructor type.,意思是无法将“protected”构造函数类型分配给“public”构造函数类型。这时候我们可以指定this的类型为(new () => T) | typeof Singleton联合类型。typeof Singleton表示Singleton类的类型,因为在类的内部可以实例化受保护的或者私有的构造函数,从而规避掉上述错误。
  • 将构造函数用protected修饰依然无法避免某些大聪明在子类内部实例化,所以构造函数内部使用了_instantiateByGetInstance判断是否是通过getInstance方法进行实例化的。当然这个问题只能在运行期间被发现。
class TestClass extends Singleton {
    testFunc() {
        new TestClass();
    }
}
抛出错误
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容