“The Singleton pattern is one of the simplest design patterns: it involves only one class which is responsible to instantiate itself, to make sure it creates not more than one instance.” — Refactoring Guru
Hey folks, welcome back to the Design Patterns series! In part one we got introduced to the world of design patterns and why they are important concepts to know when building applications with JavaScript. Now it’s time to dive deeper into one of the most common patterns — the Singleton. As the name implies, this pattern ensures that only one instance of a class can exist at any time.
Check out Part 1 of this series here: Click Here
What is a Singleton Pattern?
In essence, a Singleton pattern restricts the instantiation of a class to a single object. It provides a way to ensure that only one object of a particular class is ever created. All other instances that try to instantiate the same class will return the reference to the original, single instance.
Some key properties of a Singleton:
Only one instance of the class can be created.
It must self-instantiate itself on the first usage.
It must give access to its own instance on all subsequent usages.
So instead of creating multiple instances via new
, a Singleton class returns the same instance every time you try to access it. This works great for resources like a database connection that you want shared application-wide.
Implementing the Singleton Pattern in JavaScript
Imagine you are developing a web application that requires global access to configuration settings like API endpoints and authorization keys. These values need to be available to multiple modules/files throughout the app.
A Singleton class could be used to centralize access to this configuration data. Here is some sample code:
class Config {
static instance;
constructor() {
this.apiUrl = 'https://example.com/api';
this.authToken = '123abc';
return Config.instance || (Config.instance = this);
}
getApiUrl() {
return this.apiUrl;
}
getAuthToken() {
return this.authToken;
}
}
let config1 = new Config();
console.log(config1.getApiUrl()); // https://example.com/api
let config2 = new Config();
console.log(config1 === config2); // true
The
Config
class is designed to allow only one instance to manage application configuration settings.When you create an instance of
Config
(e.g.,config1
), it sets default values for configuration variables and ensures that only one instance is created.You can access the configuration values using methods like
getApiUrl()
andgetAuthToken()
.When you create another instance of
Config
(e.g.,config2
), it doesn't create a new instance but returns the same instance created byconfig1
.Checking if
config1
andconfig2
are the same instance using === returns true.
In essence, this code enforces a Singleton pattern for managing configuration settings in the application, ensuring there’s only one instance of the Config class.
Real-world Use Cases
Some common use cases for the Singleton Design Pattern include:
Logging —Creating a single point for logging throughout an application, ensuring that log entries are consistently managed.
Database Connection — Managing a single database connection to prevent multiple connections to the same database.
Caching — Implementing a caching system that stores frequently used data in memory, which should have a single instance to avoid redundant caches.
Configuration Management — Managing application configuration settings that should be globally accessible and consistent.
Thread Pools — When implementing a thread pool for concurrent execution, you often want to ensure that there’s a single pool shared across your application.
Device Managers — For device control in embedded systems or IoT applications, you might want to ensure that only one instance of a device manager exists.
State Management — In finite state machines or stateful applications, you may need a single state manager to maintain the current application state.
Resource Managers — Managing resources like network connections, file handles, or hardware devices to ensure efficient resource allocation.
Global Objects — Creating a single instance of a global object, like a game engine, to maintain game state and resources.
Print Spoolers — Managing print jobs in a printer spooling system, ensuring that print jobs are processed sequentially.
User Sessions — In web applications, you can use a Singleton to manage user sessions, ensuring that there’s a single source of truth for session data.
Window Managers — In GUI applications, you may want to ensure there’s a single window manager for handling and organizing windows.
Event Managers — Centralizing event handling and dispatching in event-driven systems or game engines.
When Not to Use Singleton
While the Singleton pattern can be useful in various scenarios, there are situations where it’s not the best choice. Here are some cases in which you should consider alternatives and avoid using the Singleton pattern:
Unintended Global State — Singleton can create global state, which can lead to hidden dependencies and make it challenging to reason about and test your code. If you want to avoid global state, consider other design patterns that promote more controlled and explicit sharing of state.
Testing Challenges — Singleton can make unit testing difficult. Since there’s a single global instance, it may not be possible to isolate and test components in isolation. If testability is a priority, consider using dependency injection and creating mockable instances instead.
Inflexibility — Singleton enforces a single instance, which can be inflexible when you later need multiple instances of a class. If there’s a potential for needing multiple instances in the future, a Singleton may not be the right choice.
Hidden Dependencies — When using Singleton, components that depend on the Singleton instance have a hidden dependency on it, which can lead to tightly coupled and less modular code. In more complex applications, this can hinder maintenance and extensibility.
Concurrency Challenges — If your application involves multithreading or concurrent execution, managing a Singleton instance can introduce complexities related to thread safety. You may need to implement additional synchronization mechanisms, which can be error-prone.
Global Access Abuse — In some cases, developers may misuse Singleton, relying on global access to store unrelated state or functionality. This can lead to messy and unorganized code.
Alternative Patterns — For certain use cases, alternative design patterns such as Dependency Injection, Factory, or Service Locator patterns might provide a more flexible and testable solution without the need for a Singleton.
Heavy Initialization — If creating a Singleton instance involves heavy and time-consuming initialization, it can lead to poor application startup performance. Consider lazy initialization or other methods to defer instance creation until it’s actually needed.
Distributed Systems — In distributed systems, Singleton patterns can be problematic because there may be multiple instances running across different nodes. Coordinating a single instance in such scenarios can be complex.
Global Data Management — When managing large sets of global data or resources, Singleton can become unwieldy. A more structured approach to data management might be more appropriate.
Conclusion
Overall, Singleton is a useful pattern for controlling access to shared resources in JavaScript apps. Just be mindful not to over-abstract and over-engineer where a simple object may suffice.
JavaScript Design Patterns: The Ultimate Guide — Part 1
🔥 Wait! 🔥
Give that like button some love! And if you’re feeling extra cheeky, hit follow too!
Follow me on Instagram: Click Here
Top comments (2)
Even though the code example "Config" acts like a singleton, it is not a very good example, because it is not clear for the "user" (the programmer) of the "Config" class that it actually is a Singleton.
By explicitly instantiating a Config object using the constructor, the programmer does not expect it to be a Singleton.
This very much reduces the readability, understandability and maintainability of the source code => the exact opposite of the purpose of a design pattern.
Therefore, the original description of the pattern by the Gang of Four suggests to use an explicit method for accessing the instance to make the purpose absolutely clear.
Thats the problem with those receipes. They uses constructs that should not be used for that. It seems to be that someone tries to convert some OO Patterns from Java or C++ into the JavaScript world, but that does not make any sense, I would think.