Singleton Design Pattern
From JholJhapata
The singleton pattern is a software design pattern that restricts the instantiation of a class to one single instance. This is useful when exactly one object is needed to coordinate actions across the system.
Problem
If we have some resource that can only have a single instance and you need to manage that single instance that is where Singleton Pattern plays important role. For Example, logger classes , driver object classes etc.
Example
Let’s take a look at an example, which is creating multiple objects.
class Program { static void Main(string[] args) { SingleTon obj1 = new SingleTon(); obj1.printText("obj1"); SingleTon obj2 = new SingleTon(); obj2.printText("obj2"); Console.WriteLine("Main End"); Console.ReadLine(); } } class SingleTon { private static int objectCounter = 0; public SingleTon() { objectCounter++; Console.WriteLine("objectCounter : " + objectCounter.ToString()); } public void printText(string strData) { Console.WriteLine(strData); } }
Output
objectCounter : 1 obj1 objectCounter : 2 obj2 Main End
We created 2 object for printing text log.
Solution
The common characteristics of a Singleton Pattern:
- A single constructor, that is private and parameter-less.
- The class is sealed.
- A static variable that holds a reference to the single created instance, if any.
- A public static means of getting the reference to the single created instance, creating one if necessary.
First version - not thread-safe
class Program { static void Main(string[] args) { SingleTon obj1 = SingleTon.getInstance; obj1.printText("obj1"); SingleTon obj2 = SingleTon.getInstance; obj2.printText("obj2"); Console.WriteLine("Main End"); Console.ReadLine(); } } class SingleTon { private static int objectCounter = 0; private static SingleTon instance; private SingleTon() { objectCounter++; Console.WriteLine("objectCounter : " + objectCounter.ToString()); } public static SingleTon getInstance { get { if(instance == null) instance = new SingleTon(); return instance; } } public void printText(string strData) { Console.WriteLine(strData); } }
Output
objectCounter : 1 obj1 obj2 Main End
Problem 1
Example
class Program { static void Main(string[] args) { System.Threading.Tasks.Parallel.Invoke( () => obj1Fn(), () => obj2Fn() ); ; Console.WriteLine("Main End"); Console.ReadLine(); } public static void obj1Fn() { SingleTon obj1 = SingleTon.getInstance; obj1.printText("obj1"); } public static void obj2Fn() { SingleTon obj2 = SingleTon.getInstance; obj2.printText("obj2"); } } class SingleTon { private static int objectCounter = 0; private static SingleTon instance; private SingleTon() { objectCounter++; Console.WriteLine("objectCounter : " + objectCounter.ToString()); } public static SingleTon getInstance { get { if (instance == null) instance = new SingleTon(); return instance; } } public void printText(string strData) { Console.WriteLine(strData); } }
Output
objectCounter : 1 objectCounter : 2 obj1 obj2 Main End
Problem 2
Example
class Program { static void Main(string[] args) { SingleTon.SingleTonChild obj1 = new SingleTon.SingleTonChild(); obj1.printText("obj1"); SingleTon.SingleTonChild obj2 = new SingleTon.SingleTonChild(); obj2.printText("obj2"); Console.WriteLine("Main End"); Console.ReadLine(); } } class SingleTon { public class SingleTonChild: SingleTon { } private static int objectCounter = 0; private static SingleTon instance; private static readonly object lockObj = new object(); private SingleTon() { objectCounter++; Console.WriteLine("objectCounter : " + objectCounter.ToString()); } public static SingleTon getInstance { get { if (instance == null) { lock (lockObj) { if (instance == null) instance = new SingleTon(); } } return instance; } } public void printText(string strData) { Console.WriteLine(strData); } }
Output
objectCounter : 1 obj1 objectCounter : 2 obj2 Main End
Second version - simple thread-safety and sealed
class Program { static void Main(string[] args) { System.Threading.Tasks.Parallel.Invoke( () => obj1Fn(), () => obj2Fn() ); ; Console.WriteLine("Main End"); Console.ReadLine(); } public static void obj1Fn() { SingleTon obj1 = SingleTon.getInstance; obj1.printText("obj1"); } public static void obj2Fn() { SingleTon obj2 = SingleTon.getInstance; obj2.printText("obj2"); } } sealed class SingleTon { private static int objectCounter = 0; private static SingleTon instance; private static readonly object lockObj = new object(); private SingleTon() { objectCounter++; Console.WriteLine("objectCounter : " + objectCounter.ToString()); } public static SingleTon getInstance { get { lock (lockObj) { if (instance == null) instance = new SingleTon(); } return instance; } } public void printText(string strData) { Console.WriteLine(strData); } }
Output
objectCounter : 1 obj1 obj2 Main End
Third version - attempted thread-safety using double-check locking
class Program { static void Main(string[] args) { System.Threading.Tasks.Parallel.Invoke( () => obj1Fn(), () => obj2Fn() ); ; Console.WriteLine("Main End"); Console.ReadLine(); } public static void obj1Fn() { SingleTon obj1 = SingleTon.getInstance; obj1.printText("obj1"); } public static void obj2Fn() { SingleTon obj2 = SingleTon.getInstance; obj2.printText("obj2"); } } sealed class SingleTon { private static int objectCounter = 0; private static SingleTon instance; private static readonly object lockObj = new object(); private SingleTon() { objectCounter++; Console.WriteLine("objectCounter : " + objectCounter.ToString()); } public static SingleTon getInstance { get { if (instance == null) { lock (lockObj) { if (instance == null) instance = new SingleTon(); } } return instance; } } public void printText(string strData) { Console.WriteLine(strData); } }
Output
objectCounter : 1 obj1 obj2 Main End