Some time ago I blogged about Breaking dependencies on specific DI containers. That post was concerned with resolving a WCF service’s dependencies using an IInstanceProvider and how to break a tight decency on Unity. I chose to use the interface provided by the Common Service Locator library on CodePlex. But there is still a problem, and one that didn’t occur to me as I was writing the code because I was so wrapped up in the details of WCF.
In short, the use of a Service Locator is considered by some to be an anti-pattern. Having thought things over I think I have to agree.
The problem with a Service Locator is that it hides dependencies in your code making them difficult to figure out and potentially leads to errors that only manifest themselves at runtime. If you use a Service Locator your code may compile but hide the fact that the Service Locator has been incorrectly configured. At runtime, when your code makes use of the Service Locator to resolve a dependency that hasn’t been configured, an error will occur. The compiler wasn’t able to help. Moreover, by using a Service Locator you can’t see from the API exactly what dependencies a given class may have.
At least one proposed solution to this problem is to use Constructor Injection to explicitly pass all of a classes dependencies in when it is instantiated. I like the logic of this solution. You can see from the constructor signature exactly what dependencies a class has and if one is missing you’ll get a compiler error.
Let’s look at an example. Imagine some code like this:
class Program { static void Main(string[] args) { var needsDependencies = new NeedsDependencies(new ServiceLocator()); needsDependencies.CallMe(); } }
Can you tell from the above code what dependencies the NeedsDependencies class actually has? How can you tell if the ServiceLocator has been configured correctly without running the code and seeing if it fails? In fact you can only see the dependencies by looking at the internal code of NeedsDependencies:
public class NeedsDependencies { private IServiceLocator _serviceLocator; public NeedsDependencies(IServiceLocator serviceLocator) { _serviceLocator = serviceLocator; } public void CallMe() { var dependency = (Dependency)_serviceLocator.GetInstance(typeof(Dependency)); dependency.CallMe(); } }
From the source code we can see that the NeedsDependencies class actually needs an instance of the Dependency class. If we didn’t have access to the source code (e.g. we were using a library provided by a 3rd party) we’d be be none the wiser and would only see that the class needs a Service Locator.
If we remove the Service Locator and replace it with the actual dependencies we can see exactly what a class needs up front:
public class NeedsDependencies { private Dependency _dependency; public NeedsDependencies(Dependency dependency) { _dependency = dependency; } public void CallMe() { _dependency.CallMe(); } }
To call this version we’d do something like this:
class Program { static void Main(string[] args) { var NeedsDependencies = new NeedsDependencies(new Dependency()); needsDependencies.CallMe(); } }
We can see right away that NeedsDependencies there is a dependency on the Dependency class – because the constructor makes this clear - and if we fail to provide one the compiler will complain.
It is important to note that we can still use IoC to resolve dependencies and we can even use a Service Locator. The point is we shouldn’t pass the Service Locator around but should restrict its use to one place in the application, usually at at start-up when dependencies are being resolved.
References
- Service Locator is an Anti-Pattern, Mark Seemann.
- Inversion of Control Containers and the Dependency Injection pattern, Martin Fowler