Singleton Design Pattern in OOPS

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.

Understanding Singleton via Example

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:

  • Only one instance of CostEstimator is available throughout the lifetime of the program. This ensures that all departments are reporting to a single object.
  • The CostEstimator object must be easily accessible to all departments.

Solution approach without using singleton pattern

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
}

Key observations

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
}
//...

Component and structure of singleton pattern

  • Singleton (CostEstimator): It contains a static member of the same type as the class, which stores the single instance of the class, and a static method getInstance() that allows clients to access its unique instance.
  • Client (COMPANY): The part of the code that uses

singleton pattern UML structure and example

Efficient approach using singleton pattern

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.

Key observations

  • By declaring the constructor as private, we have ensured that only the CostEstimator class itself can create new instances. This ensures that there will only be one instance of the class, making CostEstimator a Singleton class.
  • Additionally, by declaring "getCost()" and "cost" as static, we ensure that any object of CostEstimator will always refer to the same instance. This means that there will only be one instance of CostEstimator throughout the entire program.

So this approach uses the Singleton pattern, which overcomes the drawbacks of the previous approaches to a great extent.

Implementation of the CostEstimator class

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:

  • addAmount(int amount): Adds the given amount to the total expenditure cost.
  • showAmount(): Returns the calculated total expenditure cost.
  • cost: A static member of type CostEstimator that stores the single instance of the class.
  • getCost(): Returns the single instance of CostEstimator stored in the static member cost.
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;
    }
}

Implementation of the COMPANY Class

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:

  • Lazy initialization: The object is created only when it is first needed. (The above code example uses lazy initialization.)
  • Early initialization: The object is initialized at the time of declaration. To use early initialization, only a small part of the CostEstimator class needs to be changed. Here's an example of how that might look:
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:

Advantages of singleton pattern

  • The Singleton pattern provides centralized access to a shared resource, making it easy to manage and control its usage.
  • Since there is only one instance of the class created, it saves resources by preventing unnecessary object creation and memory consumption.
  • Implementing the Singleton pattern is straightforward and can be easily integrated into existing systems.
  • The Singleton pattern encourages global state management, making it easier to maintain the consistency of state throughout the system.

Disadvantages of singleton pattern

  • The Singleton class violates the Single Responsibility Principle because it is responsible for maintaining its sole instance and it is also responsible for the main role of the class (in the above example, estimating costs).
  • Subclassing a Singleton class can be tricky because the constructor is private. To allow subclassing, we would need to make the constructor public, but then the class wouldn't be a Singleton anymore. Even if we make the constructor public, the implementation of the Singleton will still contain static members that will be shared with subclasses, which is often not desirable.
  • Overuse of Singleton can lead to unnecessary complexity in the code.
  • Sometimes Singleton pattern can lead to tight coupling between the Singleton class and other classes that use it. This will make it difficult to modify without affecting the other classes.
  • In multi-threaded environments, the Singleton pattern can introduce concurrency issues if it is not implemented correctly.

Real-life applications of 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 software systems, logging is a critical function that records system events, errors, and messages. A logger object that implements the singleton pattern ensures that there is only one instance of the logger class in the system. This will help us to easily maintain and access the log data.
  • In applications, managing configurations for various environments can be a complex task. A configuration manager that implements the singleton pattern ensures that there is only one instance of the configuration manager class in the system. This allows for centralized access to the configuration data.
  • In applications that require database access, the database connection can be a bottleneck for performance. A singleton database connection class ensures that there is only one connection to the database, which can be shared by all the application components that require database access.
  • Caching is a technique used to improve the performance of applications by storing frequently accessed data in memory. A cache manager that implements the singleton pattern ensures that there is only one instance of the cache manager class in the system.

Conclusion

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:

  • How singleton pattern is related to other design patterns?
  • Try modifying the getInstance() method and Singleton class to allow up to 4 instances.
  • What is the difference between early and lazy initialization of the Singleton instance? Which one is the best? What is the thread-safety issue with lazy initialization of the Singleton instance, and how do you address it?
  • How do you test a Singleton class in isolation from other classes?

Please write in the message below if you find anything incorrect, or you want to share more insight. Enjoy learning, Enjoy oops!

Share Your Insights

☆ 16-week live DSA course
☆ 16-week live ML course
☆ 10-week live DSA course

More from EnjoyAlgorithms

Self-paced Courses and Blogs

Coding Interview

Machine Learning

System Design

Our Newsletter

Subscribe to get well designed content on data structure and algorithms, machine learning, system design, object orientd programming and math.