Quick Start On Singleton Design Pattern

Singleton Design Pattern is a creational design pattern. It is one of the common design patterns used to design software applications.

What is Singleton Design Pattern?

  • Singleton term represents a single instance.
  • Singleton design pattern ensures to have only one instance of the object in the application life cycle.
  • Here are the few examples as below,
     
    • Real World - The Sun, The Earth, The Moon, A CEO
    • Software World – Logger, Connection, File, Database context etc.

Basic needs of Singleton Design Pattern?

  • Rule 1: Class should not be inheritable, mark class with ‘sealed’ keyword.
  • Rule 2: Class should not be instantiated by caller classes. Define private constructor in the class only.
  • Rule 3: Class should have static properties, mark properties with a ‘static’ keyword to make them static.
  • Rule 4: Class should be accessible anywhere/globally, mark class as ‘public’ keyword.

Quick On Singleton Design Pattern

Advantages

  • It prevents call code to create new instances of the class
  • A single instance of an object is used and available to the application to use class's functionality.

Disadvantages

  • Unit testing is very hard, we cannot run multiple tests in parallel.
  • Cannot completely isolate the classes that are dependent on the singleton.

How to implement it?

There are many ways to implement a singleton design pattern. Here, I will demonstrate the best practices to implement a singleton design pattern.

Option A

Below code defines the singleton code for Logger by using .NET Lazy type. The initialization of the object happens on demand only. The caller code calls the singleton logger with 10 times and has the same instance all the 10 times call with having the same hash code.

Implementation Code

public sealed class SingletonLogger {  
    private static readonly Lazy<SingletonLogger> _singletonLoggerObj = new Lazy<SingletonLogger> (() => new SingletonLogger());  
    private SingletonLogger() {}  
    public static SingletonLogger SingletonLoggerInstance {  
        get {  
            return _singletonLoggerObj.Value;  
        }  
    }  
}  

Client Code

List<int> hashData = new List<int>();  
for (int index = 0; index < 10; index++)
    hashData.Add(SingletonLogger.SingletonLoggerInstance.GetHashCode());  

if (hashData.Distinct().Count() == 1)
     Console.WriteLine("I am Singleton!");  
else
     Console.WriteLine("I am not Singleton!");  

Output

Quick On Singleton Design Pattern

Option B

When multiple parallel threads access the same instance at the same time to get an instance of a singleton logger, there may be a concurrency conflict on creating an instance and violating the singleton pattern.

The below code defines the singleton code for Logger that is thread-safe. When multiple parallel threads access code at the same time to get an instance of a singleton logger. The code defines a lock pad object to prevent the concurrency issue while creating a singleton instance.

Implementation Code

public sealed class SingletonLoggerThreadSafe {  
    private static readonly object _objLock = new object();  
    private static SingletonLoggerThreadSafe _instance = null;  
    private SingletonLoggerThreadSafe() {}  
    public static SingletonLoggerThreadSafe Instance {  
        get {  
            if (_instance == null) {  
                lock(_objLock) {  
                    if (_instance == null)
                        _instance = new SingletonLoggerThreadSafe();  
                }  
            }  
            return _instance;  
        }  
    }  
}  

Client Code

static void Main(string[] args) {  
    // By using - SingletonLoggerThreadSafe  
    Task<int> task1 = Task.Run(ThreadBatch1);  
    Task<int> task2 = Task.Run(ThreadBatch2);  
    if (task1.Result == task2.Result && task1.Result != -1)
        Console.WriteLine("I am Singleton!");  
    else
        Console.WriteLine("I am not Singleton!");

    Console.ReadLine();  
}  
public static int ThreadBatch1() {  
    int val = -1;  
    List<int> hashData = new List<int> ();  
    for (int index = 0; index < 50000000; index++)
         hashData.Add(SingletonLoggerThreadSafe.Instance.GetHashCode()); 

    var res = hashData.Distinct();  
    if (res.Count() == 1) {  
        Console.WriteLine("ThreadBatch1 - I am Singleton!");  
        val = res.First();  
    } 
    else
        Console.WriteLine("ThreadBatch1 - I am not Singleton!");  
    return val;  
}  
public static int ThreadBatch2() {  
    int val = -1;  
    List<int> hashData = new List<int>();  
    for (int index = 0; index < 50000000; index++)
        hashData.Add(SingletonLoggerThreadSafe.Instance.GetHashCode()); 

    var res = hashData.Distinct();  
    if (res.Count() == 1) {  
        Console.WriteLine("ThreadBatch2 - I am Singleton!");  
        val = res.First();  
    }
    else
        Console.WriteLine("ThreadBatch2 - I am not Singleton!");  
    return val;  
}  

Output

Quick On Singleton Design Pattern

Conclusion

Both of the above options are recommended. Personally, I would prefer Option A that takes advantage of lazy initialization.

Note
This is one of the common design pattern interviewers as in design pattern interview questions. 😊