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

标签 javascript typescript design-patterns singleton javascript-objects

我有一个 TypeScript 单例类

class MySingleton {
    private static _instance: MySingleton;

    private constructor();

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

        return MySingleton._instance;
    }

}

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

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

因此,这可以通过让 getInstance 接受一个传播到构造函数的选项对象来实现。那么用法就变成了这样

MySingleton.getInstance({
    prodMode: true
});

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

最佳答案

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

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

因为对象不相互依赖。

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

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

在 TypeScript 中,您可以通过创建全局变量来实现。

例如:

// at global scope
var instance = {
  prodMode: true
};

或在模块内:

globalThis.instance = {
  prodMode: true
};

declare global {
  var instance: {
    prodMode: boolean
  };
}

就是这样,没有类,没有锁或守卫,只有一个全局变量。

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

  1. 根据定义是单例
  2. 非常简单。
  3. 该对象使用起来方便、随意。

现在您可以回应您需要或至少想要一个类。

没问题:

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)的方法具有以下优点:

  1. 无需编写私有(private)构造函数来防止外部实例化。
  2. 该类不能直接实例化(尽管它可以作为 instance.constructor 访问,这是避免使用类并使用前面所述的简单对象的另一个原因)。
  3. 没有 class 可以扩展或以任何方式滥用,这会在运行时破坏单例模式(尽管它可以作为 instance.constructor 访问,这是如前所述,避免使用类并使用简单对象的另一个原因。

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

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

globalThis.instance = {
  prodMode: true,
  reconfigure(options) {
    this.prodMode = options.prodMode;
  }
};

备注:

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

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

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

模块可以提供帮助。

就个人而言,因为我使用模块,如果我想要一个不同的全局对象,这取决于诸如“正在生产”之类的东西的值(value),我会写

// app.ts
export {}

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

关于javascript - 在创建单例实例时传递选项的 TypeScript 模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49543310/

相关文章:

javascript - 是否有任何开源 Web 应用程序具有良好的 QUnit(或 JSUnit)测试用例?

reactjs - asp.net core 2.1 Webpack 4 React 无法正常启动

reactjs - 使用 createAsyncThunk 和 typescript 错误处理的 Redux 工具包

angular - Flux 与全局变量(在服务中)

java - 这个文件操作设计模式的名称是什么?

javascript - 浮点和 bool 变量的展开运算符问题

javascript - 我如何使用 Google Plus 和 Facebook Demo 不透明 Javascript?

javascript - 我想通过文本输入从用户那里获取两个整数,找到两个数字之间的差值并将该数字除以 25。我该怎么做?

angular - Typescript - Angular 测试,如何使用 ngOnInit 中的参数处理 http 请求?

design-patterns - ViewModel 是否适合 Model View Presenter 模式?