TypeScript模式在创建单例实例时传递选项

Kar*_*ari 6 javascript singleton design-patterns javascript-objects typescript

我有一个TypeScript单例类

class MySingleton {
    private static _instance: MySingleton;

    private constructor();

    public static getInstance() {
        if (!MySingleton._instance) {
            MySingleton._instance = new MySingleton();
        }

        return MySingleton._instance;
    }

}
Run Code Online (Sandbox Code Playgroud)

现在,我想在创建这个单例实例时传递一些选项.

例如,将实例的模式设置为生产模式的选项.

因此,可以通过getInstance接受传播到构造函数的选项对象来实现.然后使用变得像

MySingleton.getInstance({
    prodMode: true
});
Run Code Online (Sandbox Code Playgroud)

这是最好的方法吗?这样做有点蠢,因为当通过传递选项对象MySingleton执行的消费者时getInstance,他或她无法知道传递的选项对象将被使用或被忽略.

Alu*_*dad 5

在 TypeScript 中,就像在 JavaScript 中一样,单例模式并不存在,正如您在 Java 或其他语言中可能知道的那样。

为什么我们没有相同的概念?

因为对象不是相互依赖的。

首先我们问自己什么是单例?

在整个应用程序中全局只能有一个实例的类。

在 TypeScript 中,您可以通过创建全局变量来做到这一点。

例如:

// at global scope
var instance = {
  prodMode: true
};
Run Code Online (Sandbox Code Playgroud)

或从模块内:

globalThis.instance = {
  prodMode: true
};

declare global {
  var instance: {
    prodMode: boolean
  };
}
Run Code Online (Sandbox Code Playgroud)

就在那里,没有类,没有锁或守卫,只是一个全局变量。

上述方法具有以下额外优点(就在我的脑海中):

  1. 根据定义它是一个单例
  2. 这很简单。
  3. 该对象可以方便且毫不客气地使用。

现在您可能会回答说您需要或至少想要上课。

没问题:

globalThis.instance = new class {
  prodMode = true
}();
Run Code Online (Sandbox Code Playgroud)

但请不要将类用于简单的配置对象。

现在开始配置实例的用例:

如果你想要一个可配置的单例,你应该仔细考虑你的设计。

但是,如果经过深思熟虑,似乎有必要创建一个全局构造函数,请考虑以下调整:

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;
}
Run Code Online (Sandbox Code Playgroud)

上述使用 IIFE 模式(TypeScript namespace)的方法具有以下优点:

  1. 无需编写 aprivate constructor来阻止外部实例化。
  2. 该类无法直接实例化(尽管它是可访问的,因为instance.constructor这是避免使用类并使用前面所述的简单对象的另一个原因)。
  3. 没有任何class可以在运行时破坏单例模式的方式进行扩展或误用(尽管它是可访问的,因为这是instance.constructor避免使用类并使用前面描述的简单对象的另一个原因)。

但正如你所说,将选项传递给getInstance函数会使 API 变得不清楚。

如果目的是允许更改实例,只需公开重新配置方法(或只是使对象可变)。

globalThis.instance = {
  prodMode: true,
  reconfigure(options) {
    this.prodMode = options.prodMode;
  }
};
Run Code Online (Sandbox Code Playgroud)

评论:

JavaScript 中众所周知的单例的一些示例包括

  1. 对象对象
  2. 数组对象
  3. 函数对象
  4. (您最喜欢的库)通过脚本标签加载时

全局变量通常很糟糕,而可变全局变量通常更糟糕。

模块可以在这里提供帮助。

就我个人而言,由于我使用模块,如果我想要一个不同的全局对象,具体取决于“正在生产”之类的值,我会写

// app.ts
export {}

(async function run() {
  const singletonConditionalModuleSpecifer = prodMode
    ? "./prod-singleton"
    : "./dev-singleton";
  
  const singleton = await import(singletonConditionalModuleSpecifer);
  // use singleton
}());
Run Code Online (Sandbox Code Playgroud)