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.
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.
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.
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.
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.
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):
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:
private constructor
to prevent external instantiation.instance.constructor
which is another reason to avoid a class and use a simple object as described earlier).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
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
}());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With