Singleton Design Pattern – Head First Approach

Alyaa TalaatAlyaa Talaat
5 min read

The Singleton Pattern is a simple yet powerful design pattern for controlling object creation. It ensures that a class has only one instance and provides a global point of access to that instance. In the Head First Design Patterns book, the Singleton Pattern is presented with practical examples and demonstrates why limiting instantiation can be essential for certain use cases, such as managing a shared resource or controlling access to a system.


What is the Singleton Pattern?

Definition: The Singleton Pattern ensures that a class has only one instance and provides a global access point to that instance. This pattern is widely used to control access to shared resources, such as database connections, loggers, or configuration managers.

In real-world applications, having multiple instances of a class can lead to inconsistent behavior or wasted resources. The Singleton Pattern prevents this by managing a single, controlled instance.


Problem Scenario

Imagine you are developing a Chocolate Boiler system. You only want one instance of the Chocolate Boiler running at any given time. Multiple instances could overfill the boiler, or worse, damage the system due to conflicting processes. The Singleton Pattern solves this problem by ensuring that only one boiler exists, and all clients interact with that single instance.


Using the Singleton Pattern

Let's see how to implement the Singleton Pattern step by step. In this example, we’ll create a ChocolateBoiler class and use the Singleton Pattern to ensure there’s only one instance.

Step 1: Create the Singleton Class

public class ChocolateBoiler {
    private boolean empty;
    private boolean boiled;
    private static ChocolateBoiler uniqueInstance;

    // Private constructor to prevent direct instantiation
    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    // Get the unique instance of the ChocolateBoiler
    public static ChocolateBoiler getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new ChocolateBoiler();
        }
        return uniqueInstance;
    }

    // Fill the boiler
    public void fill() {
        if (isEmpty()) {
            empty = false;
            boiled = false;
            System.out.println("Filling the boiler with chocolate and milk mixture.");
        }
    }

    // Drain the boiler
    public void drain() {
        if (!isEmpty() && isBoiled()) {
            empty = true;
            System.out.println("Draining the boiled chocolate mixture.");
        }
    }

    // Boil the contents
    public void boil() {
        if (!isEmpty() && !isBoiled()) {
            boiled = true;
            System.out.println("Boiling the mixture.");
        }
    }

    public boolean isEmpty() {
        return empty;
    }

    public boolean isBoiled() {
        return boiled;
    }
}

Step 2: Accessing the Singleton Instance

In this example, we use the getInstance() method to ensure that there is only one ChocolateBoiler object throughout the application.

public class ChocolateFactory {
    public static void main(String[] args) {
        ChocolateBoiler boiler = ChocolateBoiler.getInstance();
        boiler.fill();
        boiler.boil();
        boiler.drain();

        // Get the unique instance again
        ChocolateBoiler anotherBoiler = ChocolateBoiler.getInstance();
        System.out.println("Are both instances the same? " + (boiler == anotherBoiler));
    }
}

Output:

Filling the boiler with chocolate and milk mixture.
Boiling the mixture.
Draining the boiled chocolate mixture.
Are both instances the same? true

In this code, even though we try to get multiple instances of ChocolateBoiler, only one instance is created. Any other attempts to get an instance will return the same object.


Important Concepts

  • Private constructor: The constructor is private, preventing the creation of new objects from outside the class.

  • Lazy instantiation: The instance of the class is created only when it is first needed. This approach avoids the overhead of creating the instance during program startup.

  • Global access: The Singleton Pattern provides a global point of access to the single instance via the getInstance() method.


Variants of Singleton Pattern

Thread-Safe Singleton

In multi-threaded environments, the Singleton needs to be thread-safe to prevent multiple threads from creating different instances at the same time.

public class ThreadSafeChocolateBoiler {
    private boolean empty;
    private boolean boiled;
    private static ThreadSafeChocolateBoiler uniqueInstance;

    private ThreadSafeChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    public static synchronized ThreadSafeChocolateBoiler getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new ThreadSafeChocolateBoiler();
        }
        return uniqueInstance;
    }
}

The synchronized keyword ensures that only one thread at a time can execute the getInstance() method, preventing multiple instances from being created in a multi-threaded environment.

Double-Checked Locking

Double-checked locking reduces the overhead of synchronization by only locking when the instance is null.

public class DoubleCheckedLockingChocolateBoiler {
    private static volatile DoubleCheckedLockingChocolateBoiler uniqueInstance;

    private DoubleCheckedLockingChocolateBoiler() {}

    public static DoubleCheckedLockingChocolateBoiler getInstance() {
        if (uniqueInstance == null) {
            synchronized (DoubleCheckedLockingChocolateBoiler.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new DoubleCheckedLockingChocolateBoiler();
                }
            }
        }
        return uniqueInstance;
    }
}

This method is more efficient in highly concurrent applications because it avoids synchronization once the instance is initialized.


When to Use the Singleton Pattern

  • Managing shared resources: For example, database connections, file managers, or loggers, where multiple instances would lead to resource conflicts.

  • Providing a single point of control: For global settings or configurations that should be consistently applied across the system.

  • Memory management: To limit the overhead of creating multiple instances, particularly for heavy objects like database connections or server sockets.


Bullet Points

  • The Singleton Pattern ensures that only one instance of a class is created, providing a single point of access to that instance.

  • It’s commonly used for managing shared resources or global configurations.

  • There are various thread-safe implementations for Singleton in multi-threaded environments.


Conclusion

The Singleton Pattern is essential when it comes to controlling the instantiation of objects, particularly when managing shared resources. While it’s one of the simpler design patterns, it plays a crucial role in ensuring that your application remains efficient and consistent, especially when dealing with sensitive or resource-heavy operations. The chocolate boiler example from Head First Design Patterns highlights how even basic object creation strategies can prevent major issues in software design.


3
Subscribe to my newsletter

Read articles from Alyaa Talaat directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Alyaa Talaat
Alyaa Talaat

As a continuous learner, I’m always exploring new technologies and best practices to enhance my skills in software development. I enjoy tackling complex coding challenges, whether it's optimizing performance, implementing new features, or debugging intricate issues.