Lets start with the creational design pattern “SINGLETON“.
Lets understand what are creational design patterns.
Creational design patterns help in controlling how objects are created in an application. Instead of creating an object using the new keyword everywhere, which can make the code tightly coupled and harder to maintain. So these creational patterns provide different ways to create objects.
Let’s say you use the new keyword to create an object in multiple places, and later decide to change how the object is created. You would then have to manually update every single place where the new keyword is used.
For example
Initially, I created the car objects using the new keyword wherever required:
Car myCar = new Car("Red", "SUV");
Car anotherCar = new Car("Blue", "Sedan");
Now, let’s say the car objects must also have a unique ID. To accommodate this change, you’ll need to modify all instances of new Car() like this:
Car myCar = new Car("Red", "SUV", 12345);
Car anotherCar = new Car("Blue", "Sedan", 67890);
If new Car() has been used in dozens or even hundreds of places, this becomes a lot of work and is also error-prone. Such changes would require extensive QA testing as well.
To avoid these scenario you can use Creation design patterns. Will explain the solution for the above issue in Factory design pattern
SINGLETON
In this article, we will discuss one of the most commonly used creational design patterns called the "Singleton design pattern."
Definition:
This Design pattern make sure a class has only one object through out the application, and give everyone a way to access that object.
Application:
This pattern is commonly used in applications when you're making a database connection or creating cloud instances, because it requires to create a connection with the database servers or cloud instances, which consume resources to store the connection data. So creating multiple connection objects is not efficient.
While implementing logging keep all loggers as Singletons which increases performance. Singleton ensures there is one logger instance used across the entire application. This helps save resources and keeps the logging system efficient and centralized.
Classes that provide configuration settings should be Singleton classes because they hold important settings that are used across the application. It makes sense to ensure there is only one instance of the configuration class.
Classes that provide shared data, like a cache, should also be Singleton classes because the data needs to be consistent throughout the application. If each part of the program creates its own instance of these shared resources, it can lead to conflicts, inefficiency, or data inconsistency.
Structure
Below is the implementation of singleton in single threaded environment.
Things to make sure in singleton pattern implementation
1.The Singleton class must have private constructor. It avoids creating obj outside the class.
2.static instance of itself.
3.getInstance method should be static to access its instance globally without creating the object.
public class Singleton {
private static Singleton instance;
private Singleton() {
System.out.println("initializing Singleton");
}
public static Singleton getInstance() {
if(instance==null) {
instance = new Singleton();
}
return instance;
}
}
Output of the above code. It has returned same instance.
Output of above code in multithread environment. It has returned different instance.
In a multithreaded environment, you need to ensure that multiple threads do not enter the if statement simultaneously. Since all threads share the same data, if two threads reach the if condition at the same time, both will see the instance as null. This could lead to the creation of two instances of the Singleton class.
public class ThreadSafeSingleton {
private volatile static ThreadSafeSingleton instance;
private ThreadSafeSingleton() {}
public static ThreadSafeSingleton getInstance() {
if(instance == null) {
synchronized(ThreadSafeSingleton.class) {
if(instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
Output of above code: Here you can observe that all the object are same though it is multithreaded.
In the above implementation, we have two null checks and allowing only the thread that holds the monitor lock to proceed. The first null check improves performance because if the instance is not null, the thread doesn't need to wait for the lock and can simply return the instance. If the instance is null, the thread must wait to acquire the lock. Once the thread acquires the lock, it needs to check for null again because the previous thread may have already created the object.
In singleton we have two forms:
1.Early loading: Here Singleton object will be created at the time of class Loading.
public class Singleton {
//instance has created at load time
private static Singleton instance = new Singleton();
private Singleton() {
System.out.println("initializing Singleton");
}
public static Singleton getInstance() {
return instance;
}
}
2.Lazy loading: Lazy Instantiation is the most recommended way of following a singleton pattern. Here the instance of the class is created when required
Hope you like article. In the next article we will discuss about Factory design pattern.
Stay tuned for more on design patterns in my next blogs. 😊