Singleton Design Pattern in OOPS

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.

Understanding Singleton via Example

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:

  • Only one instance of CostEstimator should be available throughout the program's lifetime.
  • The single instance of the CostEstimator class 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
}

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() allows clients to access its unique instance.
  • Client (COMPANY): The part of the code that uses CostEstimator class.

singleton pattern UML structure and example

Efficient approach using singleton pattern

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.

Implementation of the CostEstimator class

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().

  • 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.

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:

  • Lazy initialization: object is created only when it is first needed. (Above code uses lazy initialization.)
  • Early initialization: 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;
    }
}

Here are some of the pros and cons of using the Singleton pattern:

Advantages

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

Disadvantages

  • Singleton class violates the Single Responsibility Principle because it is responsible for maintaining its sole instance and 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.
  • Sometimes Singleton 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, Singleton can introduce concurrency issues if not implemented correctly.

Real-life applications

  • In software systems, logging is a critical function that records system events, errors, and messages. Here logger object implements the singleton pattern and ensures that there is only one instance of the logger class in the system.
  • Managing configurations for various environments can be a complex task in applications. So, we can use the singleton pattern idea to implement the configuration manager and ensure that there is only one instance of the configuration manager class. This allows for centralized access to the configuration data.
  • In database management, Singleton pattern can be used to ensure that only one instance of a database connection or database manager exists throughout the application. This is useful because opening and maintaining multiple connections to a database can be resource-intensive and may lead to inefficiencies.

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?

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.

Share Your Insights

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.