Skip to main content

Understanding Singleton Design Pattern

The Singleton pattern is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.

Reading UML Diagram

  • (-) Sign means that that member is private
  • (+) Sign means that that member is public
  • (Underlined Members) Means that that member is static

It's widely used in scenarios where having more than one instance could lead to issues, like inconsistent data or synchronization problems.

Implementation of Singleton Design Pattern

Steps of Implementation

Implementing a Singleton pattern in object-oriented programming typically involves the following steps:

  1. Declare a private static attribute in the singleton class.
  2. Create a public static method (commonly named getInstance()) as a global access point for the singleton object. This function implements the "lazy initialization" of the singleton object, i.e., it creates a new instance only when it is needed.
  3. Make the constructor of the singleton class private to prevent other objects from using the new operator with the singleton class.
  4. In the static method of the class, check if the singleton instance exists. If it does, return it. If it doesn't, create a new one and return it.
TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

Classic Implementation

In TypeScript, the Singleton pattern can be implemented as follows:

class Singleton {
// Step 1: Declare a private static instance
private static instance: Singleton;

// Step 3: Make the constructor private
private constructor() {}

// Step 2: Create a public static getInstance method
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}

public someMethod(): void {
console.log("Hello, I am a singleton!");
}
}

// Usage
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();

instance1.someMethod(); // 'Hello, I am a singleton!'
instance2.someMethod(); // 'Hello, I am a singleton!'

console.log(instance1 === instance2); // true

In the above code:

  • The Singleton class maintains a static reference to the single created instance, stored in the instance attribute.
  • The getInstance() method is the global access point for the singleton instance. It initializes the Singleton instance if it has not been initialized yet, and returns the reference to the created instance.
  • The Singleton constructor is made private to prevent other instances from being created through the new operator.
  • The someMethod() is an example of a method that can be accessed on the singleton instance.

The last line in the code (console.log(instance1 === instance2);) verifies the Singleton pattern: it checks if instance1 and instance2 refer to the same object, which they do, hence it logs true to the console. This confirms that there is indeed only a single instance of Singleton.

When To Use The Singleton Pattern

Here are some signs that might indicate that a Singleton pattern could be appropriate:

  1. Global Variables: If you notice that you're using a global variable to keep some data that should be accessible by many different parts of your system, a Singleton might be an appropriate way to encapsulate that.

  2. Repeated Initialization: If your code includes repeated, expensive initialization of the same thing (like reading configuration data from a file, setting up a database connection), it might be a good idea to use a Singleton to do this only once and then provide the result to the rest of your system.

  3. Multiple Access, Single Control: If several parts of your system are accessing and potentially modifying a shared resource, such as a data cache or a system-wide configuration, it could be a code smell that a Singleton pattern would be beneficial to encapsulate access to that resource.

  4. Non-Uniform Access: If your system contains an entity that is accessed in non-uniform or inconsistent ways across the system, encapsulating that entity into a Singleton can make its use more predictable and manageable.

  5. Duplicate Instances: If you observe that your system is generating multiple instances of an object, and each of those instances is identical and they do not maintain distinct state, you may want to consider the Singleton pattern. This could be the case if you're managing a resource that should have a single point of control, such as a print spooler or a device driver object.

  6. Excessive Parameters: If you're passing an instance of an object through several layers of your program just to make it available to a deeply nested component, consider whether this might be a sign that the object could be a Singleton.

Again, it's crucial to remember that Singleton has its downsides and should be used judiciously. It can make code harder to test, and it can introduce unnecessary statefulness and coupling into a system. Always consider whether there are other ways to achieve your goals, such as dependency injection or using a service container, before resorting to Singleton.

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for ssoftware developers.

YouTube @cloudaffle