The Singleton pattern is probably the most famous and at the same time the most controversial pattern known to us. It must be also be the simplest pattern to learn and implement.
Like any other pattern, Singleton exists to solve a common business problem that is ‘managing the state of a resource’. But does it solve the real problem or introduce additional problems?
That is exactly the topic I am covering here.
Positive sides of Singleton
One of the toughest issues to debug is the one created by the multiple instances of a class which manages the state of a single resource. It is highly desirable if we can
use some Design Pattern to control the access to that shared resource. The Singleton pattern fits the bill perfectly to solve this scenario; by wrapping a singleton class around
this problem ensures that there will be only one instance of the class at any given time. A most common and clichéd example for a singleton class is the one used for logging purposes
where the whole application needs only one logger instance at anytime.
The anatomy of a singleton class is very simple to understand. The class typically has a private constructor which will prohibit you to make any instance of the singleton class;
instead you will access a static property or static function of the singleton class to get the reference of a preconfigured instance. These properties/methods ensure that
there will be only one instance of the singleton class throughout the lifetime of the application.
The one and only instance of a singleton class is created within the singleton class and its reference is consumed by the callers. The creation process of the instance can
be done using any of the following methods:
1. Lazy Instantiation
If you opt for the lazy instantiation paradigm, then the singleton variable will not get memory until the property or function designated to return the reference is first called.
This type of instantiation is very helpful if your singleton class is resource intense.
However, the above implementation is not taking any precautions to be thread safe. That is, there may be situations like two or more threads accessing the
at the same time which will create more than one instance of the singleton class.
We can use various thread synchronization techniques to combat the circumstances said above. One way is the use of double-checked locking. In double-checked locking,
synchronization is only effective when the singleton variable is null, i.e., only for the first time call to
Instance. This helps us to limit the performance penalty that
comes along with the synchronization object to only happen once.
2. Static Initialization
In static initialization, memory is allocated to the variable at the time it is declared. The instance creation takes place behind the scenes when any of the member
singleton classes is accessed for the first time. The main advantage of this type of implementation is that the CLR automatically takes care of race conditions I explained
in lazy instantiation. We don't have to use any special synchronization constructs here. There are no significant code changes in the singleton implementation when you
switch from lazy instantiation to static initialization. The only change is that the object creation part is moved to the place where we are declaring the variable.
Inheritance of singleton class
Inheriting a singleton class should be prohibited. Making a singleton class inheritable means any number of child classes can inherit from it creating multiple
instances of the singleton class which will obviously violate the principle of singletons.
Singleton class vs. static methods
Singleton takes over static classes on the following shortcomings:
- Static classes don’t promote inheritance. If your class has some interface to derive from, static classes makes it impossible.
- You cannot specify any creation logic with static methods.
- Static methods are procedural code.
Negative sides of Singleton
The following points are used against the Singleton pattern:
- They deviate from the Single Responsibility Principle. A singleton class has the responsibility to create an instance of itself along with other business responsibilities.
However, this issue can be solved by delegating the creation part to a factory object.
- Singleton classes cannot be sub classed.
- Singletons can hide dependencies. One of the features of an efficient system architecture is minimizing dependencies between classes. This will in turn help you while
conducting unit tests and while isolating any part of the program to a separate assembly. A singleton will make you sacrifice this feature in your application. Since the object
creation part is invisible to us, we cannot expect the singleton constructor to accept any parameters. This setback may look unimportant on the first glance but as the software
complexity increases, it will limit the flexibility of the program.
Programmers often resort to the idea of Dependency Injection to overcome this problem. When dependency Injection is used Singleton instance is not retrieved
inside the class but is passed through the constructor or a property
IMathFace obj = Singleton.Instance;
SingletonConsumer singConsumer = new SingletonConsumer(obj);
In the above example,
SingletonConsumer's constructor accepts a parameter of type
IMathFace. It can be the real singleton instance or a mock object.
When to use a Singleton class?
There is no straightforward answer to this question. A scenario which is acceptable to some will be unacceptable to others.
However, it is commonly accepted that the singleton can yield best results in a situation where various parts of an application concurrently try to access a shared resource.
An example of a shared resource would be Logger, Print Spooler, etc.
The following points are suggested to be considered while designing a singleton class:
- Singleton classes must be memory-leak free. The instance of the singleton class is to be created once and it remains for the lifetime of the application.
- A real singleton class is not easily extensible.
- Derive the singleton class from an interface. This helps while doing unit testing (using Dependency Injection).
The notion of Singleton pattern replacing global variables is not always true. You should keep all the positive and negative aspects of the Singleton pattern in mind before deciding on it.