There are two patterns to implement inversion of control. The code snippet below shows the most common and easy pattern - the service locator
public class CreateCusutomerCommand { private Customer customer; public CreateCusutomerCommand(Customer customer) { this.customer = customer; } public void Execute() { var customerPersister = Container.Resolve<IPersistCustomer>(); customerPersister.Persist(customer); } }
Here we are using a static Container instance to resolve an instance of IPersistCustomer interface. This pattern slightly modified in the code below become a dependency injection pattern.
public class CreateCusutomerCommand { private Customer customer;
private IPersistCustomer customerPersister; public CreateCusutomerCommand(Customer customer, IPersistCustomer customerPersister) { this.customer = customer; this.customerPersister = customerPersister; } public void Execute() { customerPersister.Persist(customer); } }
Here we are not using a container to resolve IPersistContainer, instead, we are accepting an instance through constructor. We are declaring IPersistContainer as a dependency and enforcing it be injected through constructor. So why am I saying that service locator pattern is a trap? There are a few reasons that I have experienced myself
- The container API (call to container.Resolve<T>()) is referenced all over the places. Container becomes sticky dependency that is difficult to get rid of.
- If you are doing TDD and you want to write tests for CreateCustomerCommand class in the first example then your tests must have the knowledge of the container and what types are registered in the container.
- Tests may have a need to change the behaviour offered by the registered types in the container.
- The only way to do that would be create proxy types and register them again from the tests.
- One of the above would lead to your test project being dependent on container assemblies
If you use dependency injection pattern and wire it up properly (e.g. If I'm building an MVC project and using Autofac, I would use Autofac MVC integration and delegate the controller creation to autofac) then you would rarely need to resolve a type yourself manually. The container API calls are not littered in you code base.
The most interesting part comes when you are writing tests. It's very easy to write tests against class in the second example. I know that I need to pass in some implementation of IPersistCustomer interface. I can just mock it and there is no other setup required.
Life has become so easy with this change.
Life has become so easy with this change.