Singleton Design Pattern in action.

Design Principles — Part 3 — Creational — Singleton Design Pattern

Divesh Panwar
5 min readJan 15, 2024

In our previous discussion, we delved into the Factory Design Pattern. Now, let’s shift our focus to explore the Singleton Design Pattern in this story.

Introduction

Let us consider few examples:

  • Database Connection Pool: Manages a limited number of database connections to optimize resource usage.
  • Configuration Manager: Maintains application-wide settings accessible from anywhere.
  • Logging System: Logs messages to a single file or console, ensuring consistent output formatting.
  • Event Dispatcher: Manages event subscriptions and broadcasts events to interested parties.
  • Thread Pool: Manages a pool of threads to handle tasks efficiently.

What is common in all the above examples? At most only one Object instance is required per applicaton.

Singleton design pattern is a creational design pattern that ensures that class only has one instance at any point in time, and is accessible/available globaly.
or
The Singleton design pattern ensures that a class has only one instance and provides a global point of access to that instance. It’s useful when exactly one object is needed to coordinate actions across the system.

Key Components

  • Private constructor: Prevents direct object creation using new.
  • Static method (getInstance): Offers a controlled way to access the singleton instance, creating it if it doesn't exist yet and returning it otherwise.

Code

class ConfigurationManager {
private static instance: ConfigurationManager | null = null;
private config: any = {};

// constructor is private so that users cannot explicitly initialise a new object
private constructor(config: any | null) {
this.config = config ?? {};
}

// This function makes sure that there is only one instance of ConfigurationManager at any point in time
public static getInstance(config?: any): ConfigurationManager {
if (!ConfigurationManager.instance) {
ConfigurationManager.instance = new ConfigurationManager(config ?? {});
}

return ConfigurationManager.instance;
}

// This function helps you update the configurations
public updateConfig(newConfig: any) {
this.config = {...this.config, ...newConfig}
}

// return the configurations
public showConfig() {
return this.config ?? 'No Config Provided';
}

}

const initialConfig = {
"author": "DP",
"appURL": "medium.com"
}

const config: ConfigurationManager = ConfigurationManager.getInstance(initialConfig);

console.log(config.showConfig());
// {
// "author": "DP",
// "appURL": "medium.com"
// }

const configInsideModule: ConfigurationManager = ConfigurationManager.getInstance();

console.log(configInsideModule.showConfig());
// {
// "author": "DP",
// "appURL": "medium.com"
// }

configInsideModule.updateConfig({version: 1.0});

console.log(config.showConfig());
// {
// "author": "DP",
// "appURL": "medium.com",
// "version": 1
// }

In this example we have a class ConfigurationManager which can be used to handle the configurations of the app. If you observer carefully, the constructor of the class is private so that users cannot initialise a new instance of the class explicitly. Also the getInstance method is marked as static, which does not need a class instance to be called. As well as we have an updateConfig method so that users can update the configurations.
Everytime user tries to get an instace of ConfigurationManager, we check if an instance already exists. If the instance exists we return the instance else we create a new instance and return the instance.

Advantages

  • Single Instance: The primary advantage of the Singleton pattern is that it ensures a class has only one instance. This is particularly useful when exactly one object is required to coordinate actions across the system.
  • Global Access Point: The Singleton pattern provides a global point of access to the single instance, allowing other parts of the code to easily access and interact with it.
  • Lazy Initialization: The Singleton instance can be created lazily (i.e., only when it is needed). This can be beneficial for performance reasons, as the instance is not created until the first time it is requested.
  • Resource Management: In scenarios where a single instance is responsible for managing resources, such as database connections or file handles, the Singleton pattern helps ensure efficient resource utilization.
  • Reduces the Need for Global Variables: Instead of using global variables to store shared state, the Singleton pattern provides a more structured and encapsulated way to manage global state.
  • Thread Safety: When implemented carefully, the Singleton pattern can ensure thread safety, preventing multiple threads from creating multiple instances concurrently.
  • Easier Testing: In situations where you need to isolate and test components of your code, having a single instance can simplify testing as there’s only one object to deal with.

When not to use?

  • Highly Mutable State: If the state of the object managed by the Singleton needs to change frequently or if it needs to be reinitialized with different configurations multiple times, the Singleton pattern may not be the best fit. It’s designed for scenarios where a single instance persists throughout the application’s lifetime.
  • Global Configuration Settings: While the Singleton pattern is often used for managing configuration settings, if your application requires multiple, independently configurable instances of a class, using a Singleton might limit your flexibility.
  • Parallel Testing and Isolation: If your application requires extensive unit testing and you need to isolate components for parallel testing, the Singleton pattern may introduce challenges. The global state managed by a Singleton can make it difficult to achieve isolated testing.
  • Dependency Injection is Preferable: In modern software development, dependency injection (DI) is often considered a more flexible and testable alternative. If your codebase follows DI principles, introducing a Singleton might introduce unnecessary global state and coupling.
  • Memory Usage and Resource Management: If the Singleton manages significant resources, such as database connections or file handles, and you need fine-grained control over the creation and disposal of these resources, the Singleton pattern might not be the best choice.
  • Alternative Design Patterns: Depending on the requirements of your application, other design patterns such as Dependency Injection, Factory Method, or Abstract Factory may be more appropriate. For instance, if you need multiple instances with different configurations, the Factory pattern might be a better fit.
  • Encapsulation of Instantiation Logic: If your primary goal is to encapsulate the logic of instantiation in a single class but you don’t need to enforce a single instance, a regular class with a private constructor and static factory methods may be sufficient without the restrictions of a Singleton.
  • Forcing Singleton for Every Class: Not every class needs to be a Singleton. Applying the pattern indiscriminately can lead to an unnecessarily complex and rigid architecture.
  • Lack of Control Over Initialization: If your Singleton class has complex initialization logic that requires parameters or dependencies, you might face challenges with the inflexible initialization process imposed by the Singleton pattern.

Thank you for reading! Stay tuned for Prototype Design Pattern.

--

--