Design Patterns Unlocked: Implement Singleton in JavaScript

Design Patterns Unlocked: Implement Singleton in JavaScript

Design Patterns Unlocked: Implement Singleton in JavaScript

 

The Singleton design pattern is a software design pattern that ensures a class has only one instance and provides a global point of access to that instance. This pattern is highly relevant in scenarios where a common resource or state is shared across different parts of an application. In JavaScript, implementing the Singleton pattern can be quite distinct due to its functional nature and prototypal inheritance. In this blog post, we’ll dive into how to effectively implement the Singleton pattern in JavaScript, explore various approaches, and understand when to utilize them.

1. The Classic Singleton Pattern

At its core, the Singleton pattern restricts the instantiation of a class to a single object. A traditional way to achieve this in JavaScript involves closures. Here is a straightforward implementation:

let Singleton = (function () {   let instance;    function createInstance() {     const object = new Object("I am the instance");     return object;   }    return {     getInstance: function () {       if (!instance) {         instance = createInstance();       }       return instance;     }   }; })();  const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance();  console.log(instance1 === instance2); // true 

In this example, the Singleton pattern uses an IIFE (Immediately Invoked Function Expression) to create a private scope around the `instance` variable. The `getInstance` method checks if an instance already exists and, if not, creates one and returns it. This ensures that only one instance exists.

2. Using ES6 Classes

With the advent of ES6, JavaScript introduced classes that allowed another intuitive syntax for implementing Singletons. Here’s how you can use an ES6 class to achieve the same:

class SingletonClass {   constructor() {     if (SingletonClass.instance == null) {       SingletonClass.instance = this;     }     return SingletonClass.instance;   } }  const instanceA = new SingletonClass(); const instanceB = new SingletonClass();  console.log(instanceA === instanceB); // true 

In the ES6 class-based approach, we check the static `instance` property whenever the constructor is called. Once the instance is created, it saves it in `SingletonClass.instance`. Subsequent calls to the constructor return this instance, ensuring singularity.

3. The Module Pattern as a Singleton

Another popular method to implement a Singleton in JavaScript is the module pattern. This uses closures to offer a public API while keeping certain details private:

const SingletonModule = (function () {   let instance;    function Singleton() {     if (instance) {       return instance;     }     instance = this;     this.state = "active";     // Initialize properties or methods     return instance;   }    Singleton.prototype.getState = function () {     return this.state;   };    return Singleton; })();  const modInstance1 = new SingletonModule(); const modInstance2 = new SingletonModule();  console.log(modInstance1 === modInstance2); // true console.log(modInstance1.getState()); // "active" 

Here, the `SingletonModule` function returns the `Singleton` constructor function. If an instance already exists, it returns the existing instance. This design allows encapsulation of Private data using closures.

4. Singleton with Symbol for Unique Access

Symbols provide a new approach to create unique singleton instances. They can be used to store a single instance on an object:

const SINGLETON_KEY = Symbol(); const globalObject = {};  function SingletonWithSymbol() {   if (!globalObject[SINGLETON_KEY]) {     globalObject[SINGLETON_KEY] = this;   }   return globalObject[SINGLETON_KEY]; }  const s1 = new SingletonWithSymbol(); const s2 = new SingletonWithSymbol();  console.log(s1 === s2); // true 

By leveraging Symbol, we can create a unique and hidden key within a global object, thereby ensuring the Singleton’s uniqueness.

5. Best Practices and Use Cases

The Singleton pattern is powerful but must be used judiciously. Use Singletons in cases like configuration objects, logging, caching, and shared resources where a single instance suffices and multiple instantiations would lead to resource contention or inconsistent state.

Be mindful of using Singletons in testing. Since they are globally accessible with a single instance, they can introduce hidden dependencies and side effects in test scenarios. Consider adding reset methods or using dependency injection.

In conclusion, the Singleton pattern is a versatile tool in a developer’s toolkit. JavaScript provides several ways to implement Singletons, from classic closures to modern ES6 and Symbols, each with its advantages and trade-offs. Understanding these implementations enables effective management of shared resources and state throughout an application.

 

Useful links: