gregoryyoung is the Page Editor for the C# and DotNET topic areas. This article continues his series on design patterns. To read more, see his blog.
Nearly every programmer learns the singleton as their very first design pattern partially due its simplicity and partially to it being the first one listed in nearly every basic pattern book. It is a good introduction to the concept of design patterns and immediately people will have places within their own code to experiment with their new knowledge.
public class Singleton {
private static Singleton m_Instance ;
public static Singleton Instance {
get { return m_Instance; }
}
public void Foo() {
Console.WriteLine("foo");
}
static Singleton() {
m_Instance = new Singleton();
}
}
//Singleton implementation in VB.NET
Public Class Singleton
Private Shared m_Instance As Singleton
Public Shared ReadOnly Property Instance() As Singleton
Get
Return m_Instance
End Get
End Property
Private Sub New()
End Sub
Shared Sub New()
m_Instance = New Singleton
End Sub
End Class
Singleton.Instance.Foo() //calling a singleton
Looking at the singleton from a life cycle perspective, the first method to be called is guarenteed by the CLR to be the static constructor for the type which sets the internal instance to be an instance of the singleton class. It is important to note that the constructor for the object is marked private; this ensures that the object cannot be created externally to the singleton itself. A client seeking to access the object is forced to use the static Instance property which will always return the same instance to the caller. Thus the singleton has insured that only a single instance of the object can exist. There are two major design flaws that come up with the singleton and if not carefully implemented they can become a nightmare of a change.
1) The item which was deemed to have a maximum count of 1 and now it has a maximum count of N. Imagine a simple application that talks to a 68040 embedded system. At the time of production there is only a need for a single serial port interface so it is implemented as a singleton, but later the requirements change and multiple serial ports are needed. This problem is further made worse by the fact that when we change to having multiple objects we will likely also want to change to having multiple CONCRETE objects -- in other words we would no longer want to deal with our singleton as a concrete type but as an abstract type. An example would be if we wanted to support serial communications and IR communications with similar but functionally different protocols. Since the singleton is bound to the concrete type the move to an abstract type will cause futher issues.
2) The application changes to require multiple application domains. I see this one on a regular basis with people using the singleton pattern in ASP.NET. They develop their application in a single ASP.NET instance and then run into problems with their singleton when they move to production. The way IIS works with ASP.NET is that it starts 1..N processes running your application, and the more processes you have, the more concurrent requests you can handle. If you remember the singleton is scoped to the application domain so it is not shared between these processes. In order to share the singleton you must use a singleton that is housed in another process that the processes share -- for instance using a networked singleton through remoting.
The major problem with both of these problems is that depending on how your singleton is being used you may have MANY places that will require a change to simply make it support either multiple objects in the first case or a networked object in the second case. The reason for this is the fact that the singleton's instance method is bound to the type ex: Singleton.Instance. This will force us to change any code that is directly accessing the instance in such a manner. This is extremely important when dealing with libraries, since a simple change such as making a singleton a networked object could feasibly bubble out to every application using the library as well as the library itself.
One can help alleviate the number of places that need to be changed by passing the singleton into methods as opposed to having the methods call the singleton directly.
public void Foo() {
Singleton.Instance.Foo();
}
vs.
public void Foo(Singleton _Singleton) {
_Singleton.Foo();
}
But alas, this causes many problems as well. For example you are nine levels deep into method calls when you realize that you need an instance of the singleton to perform a task. At this point you are left with two choices: you can either pass the singleton through those nine levels of methods (changing their method signatures and breaking countless other areas of code) or you can call the instance property on the singleton type directly which will lead to future problems as discussed above.
For a reasonable answer as to how to avoid these design nightmares in our future we need to analyze exactly what a singleton does for us and how it does it.
1) The singleton guarentees that only a single instance of an object will be created by encapsulating the creation of the object in a private scope.
2) The singleton pattern includes a static factory method which allows us access to the internal instance.
Given that the singleton is actually a specialized factory method, what would happen to our system if we were to add a level of indirection by putting a factory method in front of our class?
public class SingletonFactory {
public static Singleton GetSingleton(SomeContextual _Variable) {
return Singleton.Instance;
}
}
Public Class SingletonFactory
Public Shared Function GetSingleton(ByVal Variable As SomeContextualVariable) As Singleton
Return Singleton.Instance
End Function
End Class
It is important to note that another method of obtaining a similar result is to make the singleton a normal object with an internal constructor and make the factory method actually construct it and maintain the instance. The only difference in functionality between having a true singleton and a factory which acts as a singleton is the private constructor on the object, which completely prevents any other instances from being created. If the class is internal to a library and a level of discipline can be maintained by the development team, this is often a much better approach. Also, the contextual variable is not always necesary or available but if required and identifiable should be created at the time the factory is created.
This will give us substantially more flexibility and isolate us from sweeping changes in our code base. The best part is that there is no functional difference between this and the singleton implementation we looked at earlier, yet we have removed the coupling between the singleton and the singleton type. We can now call the singleton factory whereever we want and not have to worry about passing the singleton through large amounts of code in order to use it (although in many cases it is a good practice to do so if contextual boundaries are not being crossed, as the future factory method has an unknown overhead associated with it).
This is a common case where thought ahead of time can help reduce the impact of change later. As we all know change is often times a project (or even a department) killer, the best way to keep it from killing you is to plan ahead for possible changes and isolate the scope of their effect.
Greg Young