
Creational Design Pattern - Singleton
design pattern
2 Articles
In this article, let's learn about Singleton Design Pattern
in .NET.
Table of Contents
Introduction
The singleton
pattern is a design pattern that ensures only one instance
of a class
is created and provides a way to access it globally. It's commonly used for creating objects like loggers, where you only want one instance for the entire application.
To implement the pattern, we make the class responsible for managing its own instance
by making the constructor
private
or protected
. We create a static
property or method
that returns the instance
of the class
and make sure that it's only created the first time it's
accessed (lazy
instantiation). Any other methods of the class are defined as regular instance methods and accessed through the
static
property or method.
By using the singleton pattern, we can avoid multiple instances interfering with each other, resulting in messages being logged multiple times or not at all.
Structure
Let's apply the singleton pattern structure to our real-life logger example. This pattern structure can be used for other real-life scenarios as well.
To map our logger example to the singleton pattern, we simply need to create a class
called Logger
,
which will be the singleton. The Logger class
will have a public static
property called
"instance"
, which will contain a private
backing field to store the instance value. The log method
in the Logger class
corresponds to what is known as a singleton operation in the pattern structure.
Code Sample - Singleton Pattern
- Create a
Logger class
and add aprotected constructor
to it so that clients cannot instantiate it but can subclass it. - Add a
static
Instance property and aprivate
backing field that must benullable
. - In the getter of the Instance property, check whether it's
null
. If it is, instantiate and store aLogger
instance. Always return that instance to ensure that only one instance of the Singleton class,Logger
in this case, exists at any given time. - Add a Singleton operation method, Log, for testing purposes. Every instance of the
Logger
that's returned when getting the Instance property has access to it. - Test the
Singleton
pattern by attempting to create a new instance ofLogger
, which should fail due to theprotected constructor
. Then, retrieve two instances ofLogger
viaLogger.Instance
and compare them. If they are the same instance and match theLogger.Instance
property, write that to theconsole output window
. - Log a few messages as an example to ensure that each call does the same thing.
Note: Be aware of the potential issue with Singleton pattern, which is that it can cause difficulty in testing and mocking, and can also introduce global state, leading to unexpected behavior.
Thread Safety
That's all what we've done here is a naïve form of lazy initialization. Lazy initialization
is the principle that states that we're only going
to create an instance of a class
once we need it and not when it's constructed. The problem is that this code is not guaranteed to be
thread-safe
. Let's see how to make it thread-safe!!.
Code Sample - Thread Safe Singleton Pattern
- Let's Use
.NET's
built-inthread-safe
way of dealing with lazy initialization by implementingLazy<T>
. - Add a
new static
field of type Lazy<T> to replace the oldstatic private Logger
instance, and make itreadonly
. - Initialize the lazyLogger, passing through a method for constructing the Logger when accessing the value property for the first time.
- Remove the old private static Logger instance and return the value of the lazyLogger in the property getter.
- Test the implementation to ensure it returns the same output as the previous implementation but in a thread-safe manner.
Note: In this case, Lazy of T is used to ensure thread-safe, on-demand initialization of the Singleton class. This way, the Logger instance is only constructed when needed, not at the time of the Singleton's construction, which can cause issues with thread safety.
That's all we need to do to apply the singleton pattern structure to our logger example. Now, we can move on to implementing this in a demo.
Demo - Singleton Pattern Demo
Let's try Singleton Demo, Click on the Singleton Button to see the demo on the screen.
Code Sample - Singleton Pattern Demo
Use Cases
- The singleton pattern is useful when there needs to be only one instance of a class that can be accessed from a well-known access point, like our logger example.
-
It's also helpful when we want the sole instance to be extended by subclassing, without modifying any client code, which is possible by using a
protected constructor
. - Subclassing enables you to configure your application with the desired instance of the class at runtime.
- Allow multiple instances without having to modify any client code.
Overall, the singleton pattern has certain consequences, which need to be considered before implementing it in a design.
Advantages
- Strict control over how and when clients can access it.
- Avoids polluting the namespace with global variables.
- Subclassing enables you to configure your application with the desired instance of the class at runtime.
- Allow multiple instances without having to modify any client code.
Disadvantages
Violates the single responsibility principle
by having objects control their own lifecycle. But, in modern languages, anInversion of Control (IOC)
container can handle these tasks, and the Singleton class doesn't have to manage its lifetime anymore.
Despite these points, the Singleton pattern is a useful starting point because of its simplicity. Finally, let's have a look at related patterns.
Related Patterns
abstract factory
patternbuilder
patternprototype
patternstate
pattern
Summary
In this article we learn't what is Singleton Design Pattern
, which makes sure that a class has only one instance
,
and provides a way to access it from anywhere in the code. We can create a simple implementation by adding a static Instance property and backing field
with a private or protected constructor
. However, this implementation may not be thread-safe
. A better approach is to use
lazy initialization via Lazy of T
. We covered both implementations in this article. In the next article, we will learn about the
factory method
pattern, which is one of the two factory patterns described by the Gang of Four
.