Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript pattern to pass options while creating singleton instance

I have a TypeScript singleton class

class MySingleton {
    private static _instance: MySingleton;

    private constructor();

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

        return MySingleton._instance;
    }

}

Now, I would like to pass some options while creating this singleton instance.

E.g an option that sets the mode of the instance to production mode.

So, that could be implemented by having getInstance accept an options object that gets propagated to the constructor. Then the usage becomes like

MySingleton.getInstance({
    prodMode: true
});

Is this the best way to do this? I feel kind of icky about doing this because when the consumer of MySingleton executes getInstance by passing an options object, there is no way for him or her to know that the passed options object will be used or just disregarded.

like image 436
Kartik Bhandari Avatar asked Mar 28 '18 20:03

Kartik Bhandari


People also ask

How do you create a singleton instance in typescript?

Example given: class MyClass { private static _instance: MyClass; private constructor() { //... } public static get Instance() { // Do you need arguments? Make it a regular static method instead. return this.

How can you prevent singleton implementation getting broken while creating another instance?

There are many ways to prevent Singleton pattern from Reflection API, but one of the best solutions is to throw a run-time exception in the constructor if the instance already exists. In this, we can not able to create a second instance.

Why should you avoid singletons?

The most important drawback of the singleton pattern is sacrificing transparency for convenience. Consider the earlier example. Over time, you lose track of the objects that access the user object and, more importantly, the objects that modify its properties.

What is the single ton pattern in which scenario we can use singleton pattern?

Singleton pattern is used for logging, drivers objects, caching and thread pool. Singleton design pattern is also used in other design patterns like Abstract Factory, Builder, Prototype, Facade etc. Singleton design pattern is used in core java classes also, for example java. lang.


1 Answers

In TypeScript, as in JavaScript, the singleton pattern does not exist as you might know it from Java or another language.

Why don't we have the same concept?

Because objects and classes are not interdependent.

First lets ask ourselves what is a singleton?

A class that can have only one instance globally across an entire application.

In TypeScript, you would do that by creating a global variable.

For example:

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

or from within a module:

globalThis.instance = {
  prodMode: true
};

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

There it is, no classes, no locks or guards, just a global variable.

The above approach has the following additional advantages (just off the top of my head):

  1. It is a singleton by definition
  2. It is very simple.
  3. The object can used conveniently and unceremoniously.

Now you may respond that you need or at least want a class.

No problem:

globalThis.instance = new class {
  prodMode = true
}();

But please do not use classes for simple configuration objects.

Now on to your use case of configuring the instance:

If you want to have a configurable singleton, you should consider your design carefully.

But if, after much thought, it seems necessary to create a global constructor function consider the following adjustment:

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;
}

The above approach, which uses the IIFE pattern (a TypeScript namespace) has the following advantages:

  1. There is no need to write a private constructor to prevent external instantiation.
  2. The class cannot be directly instantiated (although it is accessible as instance.constructor which is another reason to avoid a class and use a simple object as described earlier).
  3. There is no class that can be extended or otherwise misused in any way which would break the singleton pattern at runtime (although it is accessible as instance.constructor which is another reason to avoid a class and use a simple object as described earlier).

But as you say, passing options to the getInstance function makes the API unclear.

If the intent is rather to allow the instance to be changed, simply expose a reconfigure method (or just make the object mutable).

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

Remarks:

Some examples of well known singletons in JavaScript include

  1. The Object object
  2. The Array object
  3. The Function object
  4. (Your favorite library) when loaded via a script tag

Globals are generally bad and mutable globals are generally worse.

Modules can help here.

Personally, since I use modules, if I wanted a different global object depending on the value of something like "is in production", I would write

// app.ts
export {}

(async function run() {
  const singletonConditionalModuleSpecifer = prodMode
    ? "./prod-singleton"
    : "./dev-singleton";
  
  const singleton = await import(singletonConditionalModuleSpecifer);
  // use singleton
}());
like image 142
Aluan Haddad Avatar answered Sep 29 '22 00:09

Aluan Haddad