In OOP, the Singleton design pattern is a way to ensure that an application has only one instance of a particular class, which can be accessed from anywhere in the application. This instance acts like a global object that can be used to perform tasks without creating multiple copies of it.
A real-life example of this concept could be a cost estimation system for an organization, where various departments contribute costs for their respective tasks, but they report their expenses to a common object within the system. This object requires global access so that it can be accessed from any part of the program, enabling all departments to easily report their expenses. So by using this pattern, we can ensure that all departments have consistent and up-to-date information about the organization's expenses.
Let's create a system for a hypothetical company called COMPANY, which comprises two departments: Writing and Publishing. These departments contribute to the company's expenditure costs, and we aim to estimate the total cost from the present date back to the beginning.
To achieve this, we will track the costs using an instance of the CostEstimator class. So, we need to consider two main points while designing the CostEstimator class:
One solution that might come to mind is to declare a global instance of the CostEstimator class i.e. making it easily accessible to all departments.
public class CostEstimator {
public CostEstimator() {
//constructor
}
//rest part of the class
}
//declared in global scope
CostEstimator costEstimator = new CostEstimator();
void writingDepartment() {
//uses costEstimator object
}
void publishingDepartment() {
//uses costEstimator object
}
The above approach does not guarantee that there will be only one instance of the CostEstimator class. Different parts of the code can create new instances of the class by calling the constructor. For example, in the following code, calling writingDepartment() creates a new instance of CostEstimator, which is not what we want. Therefore, this approach does not meet our requirements.
//...
void writingDepartment() {
//creates a new CostEstimator instance
CostEstimator newCostEstimator = new CostEstimator();
}
void publishingDepartment() {
//uses costEstimator objec
}
//...
Now our goal is to make the CostEstimator class itself responsible for keeping track of its single instance and provide a way to access it. So if a client attempts to create a new instance, CostEstimator return the existing single instance rather than creating a new one. One way to ensure this is to make the constructor private so that the CostEstimator class can manage the creation of its instance. This means that clients won't be able to directly call the constructor to create new instances.
To keep track of the single instance, we can declare a static member called "cost" of the same type as the CostEstimator class. We can then define a static method "getCost()" to instantiate and return the single instance of the CostEstimator. If the "cost" object already exists (already been instantiated once), getCost() will simply return that instance. If "cost" doesn't exist yet, getCost() will create a new instance using the private constructor and return it.
Now we have modified the CostEstimator class into a Singleton class, which is responsible for estimating the total amount spent using the public method addAmount(int amount) and return the calculated amount using the method showAmount().
public static class CostEstimator {
private int amount;
private static CostEstimator cost = null;
private CostEstimator () {
this.amount = 0;
}
public static CostEstimator getCost () {
if(cost == null) {
cost = new CostEstimator();
}
//Implemented lazy initialization
return cost;
}
public int showAmount () {
return amount;
}
public void addAmount(int amount){
this.amount = this.amount + amount;
}
}
COMPANY class is a client class that has an instance of CostEstimator. It uses this instance to update and display the amount contained in the single instance of CostEstimator.
public static class COMPANY {
private CostEstimator cost;
public COMPANY(CostEstimator cost) {
// We use getCost() method to use the single instance
this.cost = cost;
}
public void Writing(int amountWriting) {
cost.addAmount(amountWriting);
System.out.println("Writing Department Current Total Cost:");
System.out.println(cost.showAmount());
}
public void Publishing(int amountPublishing) {
cost.addAmount(amountPublishing);
System.out.println("Publishing Department Current Total Cost:");
System.out.println(cost.showAmount());
}
}
public static void main(String []args) {
// initializing the sole instance
CostEstimator costestimator = CostEstimator.getCost();
COMPANY company = new COMPANY(costestimator);
company.Writing(40);
company.Publishing(50);
}
There are two main options for initializing the cost member:
public static class CostEstimator {
private int amount;
private static CostEstimator cost = new CostEstimator();
private CostEstimator () {
this.amount = 0;
}
public static CostEstimator getCost () {
return cost;
}
public int showAmount () {
return amount;
}
}
Here are some of the pros and cons of using the Singleton pattern:
In this article, we discussed situations in which we need to use a single instance of a class with global access, and how the Singleton creational design pattern can help us achieve this goal. We explored the design of the Singleton pattern and discussed some of its advantages and disadvantages. If you're interested in further exploring this topic, here are some ideas:
Thanks Ankit Nishad for his contribution in creating the first version of this content. Please write in the message below if you find anything incorrect, or if you want to share more insight. Enjoy learning, Enjoy oops.