This is the third post of a series on the Service Locator pattern. In the first post I described how to create a basic service locator for your C# application, while in the second post I introduced lazy initialization of the services.
We now add another piece to our puzzle, transforming the Service Locator class into a singleton.
The Singleton Pattern
The singleton is arguably the most known and controversial design pattern. Some say singletons are good, some says singletons are evil. Eric Gamma himself (one of the Gang Of Four) in a recent interview stated that (emphasis added):
When discussing which patterns to drop, we found that we still love them all. (Not really—I’m in favor of dropping Singleton. Its use is almost always a design smell.)
I don’t have a strong opinion either way; I tend to use it sparingly and in the following code I will show how to apply this pattern to the service locator. There are already countless blogs discussing the pros and the cons of the singleton pattern, therefore I won’t discuss about it any further.
The Singleton Service Locator
The following was our initial implementation (other details of the classes have been removed for clarity). The constructor was internal, allowing all potential clients from within the assembly to invoke it. Clients could either pass around a reference to the created service locator, or instantiate new instances each time:
internal class ServiceLocator : IServiceLocator { // a map between contracts -> concrete implementation classes private IDictionary<Type, Type> servicesType; // a map containing references to concrete implementation already instantiated // (the service locator uses lazy instantiation). private IDictionary<Type, object> instantiatedServices; internal ServiceLocator() { this.servicesType = new Dictionary<Type, Type>(); this.instantiatedServices = new Dictionary<Type, object>(); this.BuildServiceTypesMap(); } // rest of the methods }
To implement the singleton pattern, we make the constructor private and provide clients with a static method through which we can retrieve an instance of the service.
Note how the creation of the single instance of the ServiceLocator class is itself lazy and thread safe. There are a few variations on the theme when it comes to singleton thread safe initialization (see for example this post by Jon Skeet).
internal class ServiceLocator : IServiceLocator { // a map between contracts -> concrete implementation classes private IDictionary<Type, Type> servicesType; private static readonly object TheLock = new Object(); private static IServiceLocator instance; // a map containing references to concrete implementation already instantiated // (the service locator uses lazy instantiation). private readonly IDictionary<Type, object> instantiatedServices; private ServiceLocator() { this.servicesType = new Dictionary<Type, Type>(); this.instantiatedServices = new Dictionary<Type, object>(); this.BuildServiceTypesMap(); } public static IServiceLocator Instance { get { lock (TheLock) // thread safety { if (instance == null) { instance = new ServiceLocator(); } } return instance; } } // rest of the methods }
Clients will now simply invoke the GetService()
method through the singleton instance, without having to create a new object each time:
IServiceA service = ServiceLocator.Instance.GetService<IUniverseFileServiceAdapter>();