Design dive 1 - Singleton

To be (single) or not to be (single)?

Design patterns are a valuable tool for solving common programming problems. However, it's important to note that they are not a "silver bullet." Misusing design patterns and applying them dogmatically in every situation can lead to increased complexity and even loss of trust in the effectiveness of design patterns. It's crucial to understand each pattern's strengths and weaknesses and apply them judiciously based on the specific problem being solved.

Welcome to Design Dive, where we take a closer look at some of the most common design patterns we've explored in the past. Through this series, we aim to dive deeper into the nuances of each pattern, gaining a better understanding of their strengths, weaknesses, and practical applications.

So buckle up, and let's dive right in with our first defendant: Singleton!

What is Singleton, exactly?

Singleton is an incredibly useful creational design pattern that allows you to ensure that only one instance of a class is created and can be accessed globally throughout your codebase.

The beauty of Singleton lies in its ability to control the instantiation process of the object, which means that whenever the object is requested, the same instance is returned, rather than creating a new instance.

This can be achieved by defining a private constructor, a private static instance variable, and a public static method that provides access to a single instance.

Example:

public class Printer {
    private static Printer instance = null;
    private Printer() {}
    public static Printer getInstance() {
        // If no instance exists, create one
        if (instance == null) {
            instance = new Printer();
        }
        // if an instance already exists, return it
        return instance;
    }
}

By using this approach, Singleton creates a single point of access and ensures that the object's state is consistent throughout the application. This can be incredibly valuable in situations where you need to ensure that only one instance of a class is used, such as managing database connections or logging activities.

The intuition

When you use the Singleton pattern, you can reap numerous benefits that can help improve your application's performance and consistency.

  • Singleton enforces a single instance of a particular class within your application, ensuring that there are no conflicts or duplicated objects.

  • Singleton can help conserve memory and improve performance, as well as prevent bugs and other issues that can arise when multiple instances of the same object are used.

  • By providing a single point of access, Singleton also helps ensure that the state of the object is consistent throughout the application, which is crucial for maintaining the integrity of your data and functionality.

  • Singleton can help with the modeling of business objects that mandate the uniqueness of their instance. It is the shortest and simplest way among the patterns to manage the instance of the class

But hold on!

Although Singleton provides many benefits, there are considerable drawbacks and inefficiencies when using this pattern:

  • Generate coupling

The Singleton pattern can generate coupling because it creates a tight relationship between the code and the Singleton instance. Any code that needs to access the Singleton instance must know the Singleton class, which can make it difficult to change the Singleton implementation without affecting other parts of the code. Furthermore, because there is only one Singleton instance, any changes made to the state of that instance can impact other parts of the code that depend on it, potentially leading to unexpected consequences.

  • Multithreading problem

The Singleton pattern can make it harder to use multithreading because it introduces the possibility of race conditions and synchronization issues. Since there is only one instance of the Singleton class, multiple threads may attempt to access it simultaneously, potentially causing conflicts or inconsistencies in the state of the object. This can lead to unexpected behavior and bugs. Additionally, implementing thread-safe Singleton instances requires careful consideration of locking mechanisms and synchronization techniques, which can add complexity to the code and potentially impact performance.

  • Harder to test

Since there's only one instance of the singleton class in the entire application, testing it requires setting up the entire application state just right to test the desired behavior. This can be time-consuming and difficult, especially in large and complex applications. Additionally, since the singleton can be accessed from anywhere in the application, it can make it challenging to isolate and test individual components in isolation, leading to more complex and fragile tests.

  • Save up memory is controversial

Well, one way that the Singleton pattern can make it harder to collect garbage is by maintaining a reference to the instance throughout the lifetime of the application, even when the instance is no longer needed. This can lead to memory leaks and cause performance issues, especially in long-running applications. In some cases, it may be necessary to explicitly release the instance to free up resources, which can be error-prone and time-consuming. Proper implementation and management of Singleton instances can help mitigate these issues.

  • Prevent the use of Dependency injection

Dependency injection is a design pattern that allows for loose coupling between components. However, the Singleton pattern creates a tight coupling between components, making it harder to use dependency injection. This is because the Singleton instance is typically accessed statically, making it difficult to swap out the Singleton instance with a different implementation during runtime.

  • instantiation contract violated

As you may know, one of the principles of object-oriented programming is to allow objects to be created independently of each other. However, the Singleton pattern violates this principle by controlling the instantiation of its objects. This means that the class no longer has full control over its instantiation, which can lead to unexpected behavior and violates the instantiation contract of a class.

Conclusion

  • When applying design patterns in solving problems, you must understand that they are not capable of solving every problem that you face. Therefore, you must understand the situation that you are in to choose the best pattern.

  • Among the design patterns, the Singleton pattern has a relatively clear good use case: Use it when you want to be clear. Therefore, don’t use it just because of laziness or premature optimization. It is not worth it.

  • When programming, there are more factors to consider other than code alone: Test, performance,.etc. Therefore, it is important to keep these factors in mind when choosing the design pattern.

See more