In Object-Oriented Programming, 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 is like a global object that can be used to get things done without worrying about creating multiple copies of it.
A real-life example of this could be a cost estimation system for an organization. In this case, various departments contribute costs for their respective tasks, but they all need to report their expenses to a common object in the system. This object needs to have global access, i.e., it can be accessed from any part of the program, so that all the departments can easily access it and report their expenses. By doing so, we can ensure that all the departments have consistent and up-to-date information about the organization expenses.
Let's use the cost estimating system as an example. For this, we will create a system for a hypothetical company called COMPANY that has two departments: writing and publishing. These departments contribute to the company's expenditure costs, and we want to estimate the total cost from the present date all the way back to the beginning. To achieve this, we will keep track of the costs using an instance of the CostEstimator class.
When it comes to the CostEstimator class, there are two main points to consider:
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
}
//...
So now our goal is to make the CostEstimator class itself responsible for keeping track of its single instance. If a client attempts to create a new instance, the CostEstimator class should cancel that request and return the existing single instance. Additionally, the class should provide a way for clients to access its single instance.
One way to ensure this is to make the class constructor private. This means that clients won't be able to directly call the constructor to create new instances. Instead, we can have the CostEstimator class manage the creation of its own instance.
To keep track of its single instance, we can declare a static member called "cost" of the same type as the CostEstimator class. We can then create a static method called "getCost()" that instantiates and returns the single instance of the CostEstimator. If "cost" object already exists (if it has 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.
So this approach uses the Singleton pattern, which overcomes the drawbacks of the previous approaches to a great extent.
Now CostEstimator class has been modified into a Singleton class, responsible for estimating the total amount spent using the public method addAmount(int amount) and returning the calculated amount using the method showAmount(). Here's a summary of the methods and members in the CostEstimator class:
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. In other words, COMPANY class interacts with the CostEstimator Singleton to estimate and track the company's expenditure costs.
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);
}
We've declared the cost as a static member because we want there to be only one instance of the CostEstimator class. To access this static member, we've declared the getCost() method as static as well.
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;
}
}
The Singleton pattern has its own set of advantages and disadvantages that must be considered when deciding whether to use it in a software system. Here are some of the pros and cons of using the Singleton pattern:
The singleton pattern has a wide range of real-life applications in various software systems. Some of the common applications of the singleton pattern are:
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:
Please write in the message below if you find anything incorrect, or you want to share more insight. Enjoy learning, Enjoy oops!