Singleton Design Pattern

Introduction

The Singleton design pattern is a commonly used Creational Pattern that restricts the instantiation of a class to a single instance and provides a global point of access to it. In Simple words, we can say that this pattern allow us to create a single instance of a object. The whole system will be using that object only.

The best example for this pattern will be a configuration used by the system. As there cannot be multiple configurations active at any moment of time, So it does not make sense to create multiple configuration.

Thus, we can say that the Singleton design pattern is useful in situations where having more than one instance of a class could cause issues, such as with shared resources or configuration settings. This pattern also simplifies access to these resources or settings, as they can be accessed through a single global point of access, rather than requiring multiple instances to be managed.

Below are the main take away from the singleton pattern

  • The singleton pattern enables objects to have only one instance
  • It facilitates easy access to that instance
  • It offers control over their instantiation, such as hiding the constructors of a class

To implement the Singleton pattern, the class is designed to have a private constructor to prevent outside instantiation, and a static method is used to access the single instance. The instance is typically created the first time the static method is called, and subsequent calls to the method return the existing instance.

Example using C Language

Let us try to understand it using C language example

#include <stdio.h>
#include <stdlib.h>

// Define the singleton class
typedef struct {
    int value;
} Singleton;

// Define the static instance of the singleton class
static Singleton *obj = NULL;

// Get a reference to the singleton instance
Singleton* get_singleton_instance() {
    if (obj == NULL) {
        obj = (Singleton*) calloc(1, sizeof(Singleton));
    }
    return obj;
}

Here “get_singleton_instance()” will be used by the system to get the instance of the object. Whenever it will be called very first time, then the object will be created. Later the instance of the object will be returned by this function. So, whole system will be working on a single object to read/write the values.

// Thread A function
void* thread_a(void* arg) {
    while (1) {
        // Get a reference to the singleton instance
        Singleton* instance = get_singleton_instance();

        // Check the value of the singleton instance
        if (instance->value< 10) {
                // Update the value of the singleton instance
                instance->value = 10;
                printf("Thread A: Updated the value of the singleton instance to %d\n", instance->value);
        }

        // Sleep for 100 second before checking again
        sleep(100);
    }

    return NULL;
}

// Thread B function
void* thread_b(void* arg) {
    while (1) {
        // Get a reference to the singleton instance
        Singleton* instance = get_singleton_instance();

        // Check the value of the singleton instance
        if (instance->value > 50) {
                // Update the value of the singleton instance
                instance->value = 50;
                printf("Thread B: Updated the value of the singleton instance to %d\n", instance->value);
        }

        // Sleep for 100 second before checking again
        sleep(100);
    }

    return NULL;
}

To explain the concept, we are not considering the locking mechanism.

The above code is self-explanatory. We can see that both the threads A & B are using a simple object. This is a classic example of implementation using Singleton pattern.

C++ Example

Below is the example in C++:

#include <iostream>

class Singleton {
private:
    static Singleton* instance;
    int data;

    // Private constructor
    Singleton() {
        data = 0;
    }

public:
    static Singleton* getInstance() {
        if (!instance)
            instance = new Singleton();
        return instance;
    }

    void setData(int value) {
        data = value;
    }

    int getData() {
        return data;
    }
};

// Initialize instance to null
Singleton* Singleton::instance = nullptr;

int main() {
    // Create first instance
    Singleton* instance1 = Singleton::getInstance();

    // Set data
    instance1->setData(42);

    // Print data
    std::cout << "Data of instance 1: " << instance1->getData() << std::endl;

    // Create second instance
    Singleton* instance2 = Singleton::getInstance();

    // Print data
    std::cout << "Data of instance 2: " << instance2->getData() << std::endl;

    // Both instances point to the same object
    return 0;
}

In this example, we define a Singleton class that has a private constructor to prevent instantiation of the class from outside. Instead, we provide a getInstance() method that returns a pointer to the single instance of the class.

The instance variable is a static member of the Singleton class and is initialized to null. When getInstance() is called, it checks if the instance is null and creates a new instance if it is. Otherwise, it simply returns the existing instance.

The Singleton design pattern is a powerful tool for managing shared resources and configuration settings in an application where we are sure that the object will be unique. By ensuring that only one instance of an object exists, it simplifies access and can improve performance.

But it also has some drawbacks. Many designed & architects criticize this pattern. According to them, this single global object cab be potentially creating an unwarranted coupling between them. For example, it can be difficult to unit test, as the single instance is often shared across multiple classes. Additionally, it can be difficult to extend or modify, as the single instance can be used in many parts of an application.

Advantages of the Singleton design pattern:

  1. Controlled access to the unique instance: The Singleton pattern ensures that only one instance of a class exists, and it provides a global point of access to that instance.
  2. Global access: The Singleton pattern provides a way to access a single instance of a class from anywhere in the code.
  3. Lazy initialization: The Singleton pattern can delay the creation of the object until it is needed, which can improve performance.
  4. Thread-safety: The Singleton pattern can be implemented to be thread-safe, which ensures that multiple threads can access the object safely.

Disadvantages of the Singleton design pattern:

  1. Testability: Because a Singleton creates a global point of access to an object, it can be difficult to test classes that depend on it.
  2. Global state: The Singleton pattern can introduce global state into an application, which can make it harder to reason about and maintain.
  3. Overuse: The Singleton pattern can be overused, leading to unnecessary restrictions and reduced flexibility in the design.
  4. Lifetime management: The Singleton pattern can create issues with object lifetime management, as the Singleton instance may need to be cleaned up at a specific point.

References

Related posts