top

Search

Software Key Tutorial

.

UpGrad

Software Key Tutorial

Singleton Design Pattern

Introduction

The Singleton design pattern is a creational pattern that ensures that only one instance of a class is created throughout the entire runtime of an application. It provides a global point of access to this instance, allowing other objects to easily use it.

Overview

The Singleton pattern is useful in scenarios where having multiple instances of a class could lead to issues or unnecessary resource consumption. Some common examples include Database connection, logging and configuration settings. 

When Will We Need Singleton Pattern?

The Singleton pattern is typically used in situations where you need to ensure that only one instance of a class is created and that it can be accessed globally. Here are some scenarios where the Singleton pattern is commonly employed:

1. Database Connections: To carry out various actions when working with a database, it is frequently essential to create a connection. Multiple database connections might use a lot of resources and result in problems like connection pool exhaustion. 

2. Caching: In applications that involve caching data, a Singleton pattern can be used to manage the cache instance. A single cache instance can be shared by different parts of the application, ensuring that the data remains consistent and reducing redundancy.

3. Logger: For monitoring and debugging purposes, logging is a crucial component of any application. You can create a single logger object that can be accessed by several application components by using the Singleton design. Logs are aggregated and consistent as a result.

4. Configuration Settings: Global configuration settings are often required in applications, such as application-wide settings or environment variables. Implementing these settings as a Singleton allows easy access to the configuration values from anywhere in the codebase.

5. Thread Pools: In multithreaded applications, managing thread pools with the Singleton design might be advantageous. It enables the application's various components to share a pool of threads for task execution, ensuring effective resource utilization.

6. GUI Components: In programmes that use graphical user interfaces (GUIs), there are times when you just need to build a single instance of a specific component, like a prominent window or a dialogue box. 

How Does Singleton Design Pattern Work?

The Singleton pattern typically involves the following characteristics:

  • Private Constructor: The class implementing the Singleton pattern has a private constructor, preventing other classes from directly instantiating it.

  • Static Instance: The class provides a static method to access the single instance, usually named something like getInstance(). This method is responsible for creating the instance if it doesn't exist or returning the existing model.

  • Single Instance: The class holds a static reference to the single instance, which is created when getInstance() is called for the first time.

Here's an example of a Singleton implementation in Java:

public class Singleton {
    private static Singleton instance;
    private Singleton() {
        // Private constructor
    }
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Implementation:

The Singleton pattern can be applied in numerous ways. The prior example used lazy initialization, where the instance is only created on the first call to getInstance(). But it can be difficult to get a 100% Singleton because there are so many things to take into account, like thread safety and avoiding serialization or cloning.

Achieving 100% Singletons:

1. Eager Initialization: In this approach, the instance is created eagerly at the time of class loading. It guarantees that the instance is always available, but it may consume resources even if it's not needed immediately.

2. Thread-Safe Initialization: To ensure thread safety, you can synchronize the getInstance() method or use double-checked locking. However, synchronization can impact performance.

Lazy Initialization:

Lazy initialization means the instance is created only when it is needed. It can be implemented as shown in the previous example. However, this implementation is not thread-safe. To achieve thread safety, you can use synchronization or employ the double-checked locking technique.

Using Enums:

A more modern approach to implementing singletons is by using enums. Enum values are instantiated only once by the JVM, making them inherently singletons. Here's an example:

public enum Singleton {
    INSTANCE;
    // Singleton methods and attributes can be defined here
}

Method 1: Classic Implementation

The classic implementation of the Singleton pattern involves lazy initialization of the instance. Here's an example:

public class Singleton {
    private static Singleton instance;
    private Singleton() {
        // Private constructor
    }
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Method 2: Make getInstance() synchronized

To ensure thread safety and prevent multiple instances from being created, you can make the getInstance() method synchronized. Here's an example:

public class Singleton {
    private static Singleton instance;
    private Singleton() {
        // Private constructor
    }
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Method 3: Eager Instantiation

In the eager instantiation approach, the instance is created eagerly at the time of class loading, ensuring that it is always available. Here's an example:

public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {
        // Private constructor
    }
    public static Singleton getInstance() {
        return instance;
    }
}

Method 4 (Best): Use "Double Checked Locking"

The "Double Checked Locking" technique combines lazy initialization with synchronization to achieve both thread safety and performance. Here's an example:

public class Singleton {
    private volatile static Singleton instance;
    private Singleton() {
        // Private constructor
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton. class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

The "Double Checked Locking" approach is considered the best practice for implementing the Singleton pattern as it provides both thread safety and efficient lazy initialization.

Understanding Early Instantiation of Singleton Pattern

The Singleton pattern's early instantiation refers to the creation of the singleton instance during class loading. To put it another way, the instance is eagerly constructed before it is truly required. Here is a Java illustration:

public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {
        // Private constructor
    }
    public static Singleton getInstance() {
        return instance;
    }
}

Understanding Lazy Instantiation of Singleton Pattern

The singleton instance is only produced when it is requested for the first time under the lazy singleton approach. To put it another way, the instance is haphazardly generated when it is truly required. Here is a Java illustration:

public class Singleton {
    private static Singleton instance;
    private Singleton() {
        // Private constructor
    }
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Significance of Classloader in Singleton Pattern

In situations when the Singleton instance is produced via class-loading processes, the classloader is crucial to the Singleton pattern. Classes are loaded into memory, and instances of those classes are created by the class loader.

The Singleton instance is often created when the class loader loads the class. This guarantees that the instance will be accessible and available for the duration of the application. The class loader, which loads the class and controls its lifetime, ensures that just one instance of the Singleton class is produced.

Significance of Serialization in Singleton Pattern

The process of transforming an item into a byte stream, which may be saved in a file or sent over a network, is referred to as serialization. The opposite of recreating the object from the byte stream is deserialization.

Serialisation can have the following effects about the Singleton pattern:

  • Maintaining the Singleton state the deserialized object may lose the Singleton state when a Singleton object is serialized and subsequently deserialized. In other words, it won't be the same Singleton instance when the object is deserialized.

  • Multiple instance creation It is possible to produce numerous instances of a Singleton during the deserialization process if a Singleton class is not correctly built to handle serialization. The Singleton pattern's requirement for a single instance may be broken by this.

Understanding Real Example of Singleton Pattern

One real-life example where the Singleton pattern can be applied is a logging system. A logging system is typically a centralized component that handles logging messages from various parts of an application. It's desirable to have a single instance of the logging system throughout the application to ensure consistent logging behavior and avoid creating multiple logging instances.

Here's a simplified example in Java:

public class Logger {
    private static Logger instance;
    private Logger() {
        // Private constructor
    }
    public static synchronized Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }
    public void log(String message) {
        // Logging implementation
        System.out.println(message);
    }
}

Pseudocode

Here's a pseudocode representation of the Singleton pattern:

class Singleton
    private static instance: Singleton
    private constructor()
        // Private constructor to prevent external instantiation
    public static getInstance() : Singleton
        if the instance is null
            instance = new Singleton()
        return instance

Language Specific Code

Below listed are some of the language specific codes:

1. Singleton Design Pattern JavaScript:

// Singleton using a closure and IIFE (Immediately Invoked Function Expression)
const Singleton = (function () {
    let instance;
    function createInstance() {
        // Private variables and methods can be defined here
        return {
            // Public methods and attributes can be defined here
        };
    }


    return {
        getInstance: function () {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();


// Usage
const singletonInstance1 = Singleton.getInstance();
const singletonInstance2 = Singleton.getInstance();
console.log(singletonInstance1 === singletonInstance2); // Output: true

2. Singleton Design Pattern Python:

class Singleton:
    _instance = None


    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            # Initialization can be done here
        return cls._instance
# Usage
singletonInstance1 = Singleton()
singletonInstance2 = Singleton()
print(singletonInstance1 is singletonInstance2)  # Output: True

3. Singleton Design Pattern Java:

java
Copy code
public class Singleton {
    private static Singleton instance;
    private Singleton() {
        // Private constructor
    }
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
// Usage
Singleton singletonInstance1 = Singleton.getInstance();
Singleton singletonInstance2 = Singleton.getInstance();
System.out.println(singletonInstance1 == singletonInstance2); // Output: true

4. Singleton Design Pattern C#:

public class Singleton
{
    private static Singleton instance;
    private Singleton()
    {
        // Private constructor
    }
    public static Singleton get instance()
    {
        if (instance == null)
        {
            instance = new Singleton();
        }
        return instance;
    }
}
// Usage
Singleton singletonInstance1 = Singleton.GetInstance();
Singleton singletonInstance2 = Singleton.GetInstance();
Console.WriteLine(singletonInstance1 == singletonInstance2); // Output: True

5. Singleton Design Pattern C++:

class Singleton {
public:
    static Singleton& get instance() {
        static Singleton instance;
        return instance;
    }
    // Other public methods and attributes can be defined here
private:
    Singleton() {
        // Private constructor
    }
    // Private copy constructor and assignment operator to prevent cloning
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};
// Usage
Singleton& singletonInstance1 = Singleton::GetInstance();
Singleton& singletonInstance2 = Singleton::GetInstance();
cout << (&singletonInstance1 == &singletonInstance2) << endl; // Output: 1 (True)

6. Singleton Design Pattern PHP:

class Singleton {
    private static $instance;
    private function __construct() {
        // Private constructor
    }
    public static function getInstance() {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}
// Usage
$singletonInstance1 = Singleton::getInstance();
$singletonInstance2 = Singleton::getInstance();
echo ($singletonInstance1 === $singletonInstance2) ? 'true' : 'false'; // Output: true

In all the examples above, the Singleton pattern ensures that only one instance of the class is created and shared throughout the application. The static method getInstance() is used to access the single instance of the class.

Pros and Cons of Singleton Design Pattern

Below listed are the pros and cons of Singleton design pattern example: 

Pros:

  • Single Instance: The Singleton design pattern example in Java guarantees that only one instance of a class is created and offers a single point of access to it globally.

  • Global Access: The Singleton instance can be accessed from any location within the application, making it simple to use and eliminating the need to transmit instances back and forth between different sections of the code.

  • Resource Optimisation: Using a Singleton can improve resource utilization, particularly in circumstances where it would be inefficient to create numerous instances, like database connections or thread pools.

  • Consistent State: Because all components access the same instance with a singleton, you can keep the application's state constant.

Cons:

  • Global State: The Singleton pattern introduces a global state, which can make the codebase more complex and harder to manage. Changes to the Singleton can impact the behavior of other components that depend on it.

  • Testing Challenges: Testing code that depends on Singletons can be challenging since they introduce global dependencies. It may be difficult to isolate and test components independently.

  • Tightly Coupled: The use of Singletons can lead to tight coupling between components, making it harder to change or replace dependencies in the future.

  • Multithreading Concerns: Implementing thread safety in Singleton instances can add complexity and may impact performance due to synchronization overhead.

Difference between Singleton Pattern vs Flyweight Pattern

Singleton Pattern:

  • The Singleton pattern ensures that only one instance of a class is created and provides global access to it.

  • It is primarily used to control object creation and restrict the instantiation to a single instance.

  • Singleton pattern focuses on creating a single instance of a class that can be shared and accessed globally.

  • It provides a centralized point of access to a shared instance across the application.

Flyweight Pattern:

  • The Flyweight pattern is used to minimize memory usage by sharing a common state between multiple objects.

  • By sharing common data across several instances, it is primarily used to optimize performance and memory usage.

  • To consume less memory, the flyweight pattern concentrates on the effective sharing and reuse of objects.

  • With fewer common items, it offers a mechanism to represent huge numbers of fine-grained things.

Conclusion

When you want to make sure that only one instance of a class is produced and sold abroad, the Singleton design is a useful plan design. It offers a practical way to manage the production of items, improve asset utilization, and maintain a trustworthy state throughout the application. When planning your applications and choosing the best plan designs for your unique needs, you can make more educated decisions if you are aware of the strengths and weaknesses of these instances.

FAQs

1. Can we subclass a Singleton class?

Yes, just like other classes, Singleton classes can be subclassed. The subclass will not, however, completely adhere to the Singleton pattern because, if not carefully built; it may have numerous instances.

2. Is the Singleton pattern thread-safe?

The classic implementation of the Singleton pattern without any synchronization is not thread-safe. However, thread safety can be achieved by using synchronization, double-checked locking, or other thread-safe mechanisms.

3. Can we create multiple instances of a Singleton using reflection?

Yes, it is possible to create multiple instances of a Singleton using reflection. However, it requires explicit manipulation of the class's private constructor and is considered an edge case.

Leave a Reply

Your email address will not be published. Required fields are marked *