How not to use dependency injection: service locators and injection mania.
Development teams can struggle with dependency injection, often because they don't have a clear understanding of how best to use it.
Dependency injection is pretty straightforward really. You just pass dependencies directly into a class rather than letting the class work them out for itself. This is often done through an Inversion of Control (IoC) container which takes responsibility for resolving these dependencies and injecting them. The end result should be looser coupling between components so it's easier to switch implementations, particularly when it comes to injecting mocks for testing.
As with many patterns it can be abused, more often than not because of an incomplete understanding of the problems it's supposed to be solving.
Using a containers as a service locator
A common mistake is to treat the IoC container as a static or singleton class that is referenced throughout the code whenever you need to resolve a dependency. This is based on a misunderstanding of how dependency injection should work. You should always apply the “Hollywood principle” to a container – i.e. don't call your container, it will call you.
A container that is directly referenced by classes who want a dependency is more of a service locator. This is a very different type of pattern where you delegate the management of service references to a single class. It's regarded by some as an anti-pattern as it introduces an unnecessary dependency on the service locator and can make code more difficult to understand and maintain.
The code below demonstrates this mis-use of an IoC container. The class's constructor is using the container's Resolve method to figure out the IService dependency. This is not dependency injection as nothing is being injected into the class – you can tell from the fact that the constructor does not have any parameters.
public class ExampleClass { private readonly IService _service; public ExampleClass() { _service = Container.Resolve<IService>(); } public void DoSomething(int id) { _service.DoSomething(id); } }
This approach introduces an unnecessary dependency between the class and the IoC container. It conceals what the dependencies really are as you have to check through the detail of the code to figure out what's being resolved. This also makes testing more difficult as you have to use the IoC container to supply the service in a test rather than being able to directly pass in a mock.
The example below shows a much clearer example that uses genuine dependency injection.
public class ExampleClass { private IService _service; public ExampleClass(IService service) { _service = service; } public void DoSomething(int id) { _service.DoSomething(id); } }
Not only does this example reduce coupling but it's much easier to tell what the dependencies are as they are sitting in the constructor. The responsibility for injecting dependencies has been fully delegated to the IoC container and the class does not need to know any detail around how these dependencies were resolved. This will make it easy to pass in mocks for testing rather than having to rely on another component to supply the dependency.
Developers often find this complete separation difficult to work with when they first start using dependency injection. It can seem like voodoo to them. You only interact with a container at the start of the application to register and resolve your dependencies. In true dependency injection there's no reason why your consuming classes should ever have to deal with the container.
Injection mania
As with any popular pattern, there is a danger that dependency injection can become a hammer for every nail. You don't have to inject absolutely everything in your application. It can be easy to get carried away with dependency injection and build a towering dependency graph that is unnecessary and even counter-productive.
Dependency injection can give you greater flexibility over your component implementations and arrangements. However, you should only use it when you really need this flexibility. You should aim to minimize coupling but the wrapping and injecting of every dependency in sight can start to seem like a kind of injection mania.
Take the example of wrapping a logging framework such as log4net. The rationale is that the extra abstraction will make it easier to inject a different logging provider, but would you ever really need to? The logging functionality is already abstracted behind the ILog interface and you can easily swap in different implementations, including mocks. It might not strictly adhere to the dependency inversion principle, but you still have a flexible implementation.
The risk is that in wrapping a library you will reduce the available functionality and undermine performance. You are unlikely to come up with a meaningful abstraction that can be easily applied to any logging product and you aren’t really gaining any genuine flexibility over the implementation. It's just another layer of code.
A similar argument can be applied to wrapping the IoC container itself. This is often done to reduce any dependency on a particular product. It's particularly unnecessary in this case as a good dependency injection implementation should only reference the container during application start-up. Swapping in a different product should be pretty trivial in a well-designed application.
Configuration doesn't always have to involve XML
Most IoC containers offer you a choice of using code or file-based XML to define and configure your dependencies. It may be tempting to use XML as you will have the flexibility to change dependencies without a recompile. However, this is flexibility that you often will not need and it comes at the cost of greater complexity and fragility.
File-based configuration gives you absolutely no strong-typing or compiler support. It gives rise to large and unwieldy configuration files that are generally more difficult to read than code based configuration. More seriously, errors in file-based configuration are not picked up until run-time and they can be pretty time-consuming to fix. Code-based configuration gives you the benefit of compile-time checking so that if the build has succeeded you know you are good to go.
The one advantage of XML configuration is that it lets you change your dependencies without having to re-compile the application. This can be very useful, but is it something that you really need? Are you actually going to supply different implementations of a component that you want to be able to switch on the fly? A more robust approach may be to combine code-based configuration and application settings to switch between different sets of dependencies.
You don't have to use a container
All dependency injection really means is giving components their dependencies through their constructors, methods or properties. The application logic in the component is separated from the business of creating and managing any dependent services.
You don't have to use an IoC container to do this. All a container brings to the party is a convenient means of configuring and resolving larger orchestrations of dependencies. If you only have a small number of components in a simple application then it can make sense to forego a container and wire up the dependency injection manually.
Then again, you don't even have to use dependency injection
Dependency injection is a pattern like any other. It's there to solve specific problems and it is not appropriate in every case. Any component or pattern that you adopt should be a considered choice rather than something you reach for by default.
Dependency injection is effective when you need to inject dependencies into multiple components, use different configurations of dependencies or support different implementations of the same dependency. If you will never need to mix up your dependencies or provide different implementations then dependency injection is just a solution looking for a problem.