在 TypeScript 中,就像在 JavaScript 中一样,单例模式并不存在,因为您可能从 Java 或其他语言中知道它。
为什么我们没有相同的概念?
因为 objects 和 classes 不是相互依赖的。
首先让我们问自己什么是单例?
在整个应用程序中全局只能有一个实例的类。
在 TypeScript 中,您可以通过创建一个全局变量来做到这一点。
例如:
// at global scope
var instance = {
prodMode: true
};
或从模块内:
globalThis.instance = {
prodMode: true
};
declare global {
var instance: {
prodMode: boolean
};
}
就是这样,没有类,没有锁或守卫,只是一个全局变量。
上述方法还有以下额外的优点(我想不到):
- 根据定义它是一个单例
- 很简单。
- 该对象可以方便而毫不客气地使用。
现在你可以回应你需要或至少想要一个课程。
没问题:
globalThis.instance = new class {
prodMode = true
}();
但请不要将类用于简单的配置对象。
现在介绍配置实例的用例:
如果你想要一个可配置的单例,你应该仔细考虑你的设计。
但是,如果经过深思熟虑,似乎有必要创建一个全局构造函数,请考虑以下调整:
namespace singleton {
class Singleton_ {constructor(options: {}){}}
export type Singleton = Singleton_;
var instance: Singleton = undefined;
export function getInstance(options: {}) {
instance = instance || new Singleton_(options);
return instance;
}
上述使用 IIFE 模式(TypeScript namespace)的方法具有以下优点:
- 无需编写
private constructor 来防止外部实例化。
- 无法直接实例化该类(尽管可以以
instance.constructor 访问它,这是避免使用类并使用前面所述的简单对象的另一个原因)。
- 没有
class 可以以任何方式扩展或以其他方式滥用,这会在运行时破坏单例模式(尽管它可以作为instance.constructor 访问,这是避免使用类并使用简单对象的另一个原因前文所述)。
但正如您所说,将选项传递给 getInstance 函数会使 API 不清楚。
如果意图是允许更改实例,只需公开一个重新配置方法(或只是使对象可变)。
globalThis.instance = {
prodMode: true,
reconfigure(options) {
this.prodMode = options.prodMode;
}
};
备注:
JavaScript 中一些众所周知的单例示例包括
- Object 对象
- 数组对象
- 函数对象
- (您最喜欢的库)通过脚本标签加载时
全局变量通常不好,可变全局变量通常更糟。
模块可以在这里提供帮助。
就个人而言,由于我使用模块,如果我想要一个不同的全局对象,具体取决于“正在生产”之类的值,我会写
// app.ts
export {}
(async function run() {
const singletonConditionalModuleSpecifer = prodMode
? "./prod-singleton"
: "./dev-singleton";
const singleton = await import(singletonConditionalModuleSpecifer);
// use singleton
}());