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:
- Declare a private static attribute in the singleton class.
- 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. - Make the constructor of the singleton class private to prevent other objects
from using the
new
operator with the singleton class. - 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.
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 DETAILSClassic 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 theinstance
attribute. - The
getInstance()
method is the global access point for the singleton instance. It initializes theSingleton
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 thenew
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:
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.
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.
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.
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.
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.
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.