Introduction

This series started with describing code coupling and why it should be kept low. Later posts covered how to reduce coupling through use of a service locator and dependency injection.

This particular installment is going to explain Inversion of Control (hereafter generally called IoC), how it relates to the previous techniques, and why it is overall a better solution.

Leading to IoC

To start the explanation, I will briefly recap the techniques I previously discussed: service location and dependency injection (review the previous articles, linked above, for more detail on each). The IoC technique will be explained afterwards, building on the previous two.

Service Location

Service location is a step on the way to reducing code coupling. It encourages abstraction by decoupling construction of a object from use of the same. This is the right place to introduce an interface to represent the object, so that the implementation may be changed independently, and so that other implementations can be added without the consuming code being any the wiser. This is a big gain from reducing coupling: more changeability.

Despite the above, this technique does not go very far in reducing coupling; part of the issue is that dependencies are still internal to the class, never mind that they are constructed outside the same class by the service locator. Code consuming the class in question has no idea of the dependencies involved. As a result, unit testing is still complex, as the service locator needs to be configured to provide fake versions of registered dependencies.

The other pressing issue is that using the service locator causes some coupling to the locator itself. Ironically, this small increase in coupling happens while the tool is being used to reduce coupling! Any classes that use the service locator will have to reference it, possibly multiple times. This itself is an unfortunate side effect, although on the whole the technique can still reduce overall coupling.

Dependency Injection

As previously mentioned, a downside of service location is that dependencies are known only to the concerned class. Consuming code does not know about those dependencies, and has no way to control what is provided to satisfy them. A solution to this is dependency injection, which is like it sounds: it is a means for a class to tell the outside world what it needs, and how those needs should be provided, or injected.

Unlike service location, the dependency injection technique has no dependency on any tool or library. That alone represents some small reduction in coupling.

Dependency injection is usually done via the constructor. This would require consuming code to create and provide the dependencies, but the responsibility can be offloaded via the poor man’s technique. This has the caveat of a default constructor that still knows about the concrete dependencies, which somewhat negates the purpose of the technique. This issue can be reduced using the hybrid poor man’s technique; the difference is that the default constructor retrieves the necessary dependencies from a service locator and passes them to the normal constructor. The rest of the code remains the same; the normal constructor cares not where the dependencies come from.

Having finished the overview of service location and dependency injection, it is time to build on those concepts and look at inversion of control.

Inversion of Control

IoC is a pattern, and is about changing the control of code from one location to another. DI is, in fact, a form of IoC; it changes where a class dependency comes from – not from within, but from without. That is inverting the control of the dependency. When you use DI, you are implementing IoC!

Implementation of IoC ensures that code is cleanly separated into distinct responsibilities, with each component handling its own work and nothing else. Components may be aware of each other, but not of how others do their work. As a result, a component can have its inner workings changed, or be replaced completely, and the other components are none the wiser.

That is the IoC pattern in a nutshell. It isn’t especially complicated, but it does take a little work to fully understand; some mental retraining is required to ensure that code is written in a way to follow the IoC pattern.

The main takeaway: IoC is not DI, but DI is IoC. It’s important to distinguish them properly; I see them being used interchangeably many times on the web.

Service location and dependency injection do not intrinsically handle nested dependencies. This where a particular dependency may have its own dependencies, any of which may have their own dependencies, and so on. This is a dependency tree, also known as an object graph. Object graphs can be created in code, via the poor man’s and/or the hybrid poor man’s techniques. As shown in the DI article, this can be a tedious task involving creation of overload constructors where needed. This is not a serious issue in a small project, but bigger and more complex ventures can do with some automation.

IoC Frameworks

Recall that IoC is a pattern, not a tool. Code can be written manually to follow the pattern (e.g. dependency injection) but as mentioned, this can get tedious, the poor man’s technique notwithstanding. The process can be largely automated with the help of IoC frameworks.

There are numerous tools in this space for .NET. They include, but are not limited to:

I’m not going to go into the differences of each and detail what they can and can’t do, as that is not the focus of this article. Indeed, that is another article – or series – all on its own! Here and now, I’ll refer to IoC containers in a general sense, for the purpose of reducing coupling. They do have the same overall capabilities, and that is enough for now.

I will stop to point out one of the benefits that containers provide: when a particular object is generated by a container, the container will also generate that object’s dependencies, and their dependencies – essentially a full object graph. This would otherwise have to be done via poor man’s, or the hybrid variation, which is more code and more hassle. A properly-configured container can handle the object graph creation for you.

To make use of a container, it must first be configured. For a web project, a good place to do this is in the Application_Start method of Global.asax. Configuring a container usually involves registering classes of interest along with their dependencies. Service interfaces can be registered with their implementations. This registration process is also known as wiring, since it is making the connections. How the configuration is done differs widely from one product to another, and is not the focus here, so I’ll now move on to usage.

Some containers can be set up to intercept attempts to instantiate an object, inject the appropriate dependencies, and send the initialized object to wherever it was requested from. So beyond setup and configuration, you needn’t worry about the container at all. This is the ideal scenario – keeping coupling low, remember!

Other containers can be used as – are you ready? – a service locator! Yes, it’s true…after the progression of techniques to reduce coupling, we’ve come full circle back to service location.

If you are thinking of the disadvantages of using a service locator, realize this: when your project is using an IoC container, you’ve already implemented the IoC pattern. In this case, you can implement the hybrid poor man’s technique where needed and call it a day. The IoC container would take the place of the basic service locator that was discussed previously, and you get the benefits of reduced coupling and improved testability.

Actually, it’s not quite that simple! Read on to find out why.

Abstraction, abstraction!

Remember that the point of this series is about reducing code coupling. If you write code that directly uses one of the aforementioned containers (purposed as a service locator), you are in fact defeating the purpose of lessening code coupling: you are coupling your code to the IoC framework!

There is a solution in the form of the Common Service Locator project.

The Common Service Locator library was created in response to a need to shield normal code from knowing what IoC container is in use. Direct coupling to the concrete container is worth avoiding. Common Service Locator achieves this by introducing an interface with a set of standard methods for resolving dependencies. The interface is shown below:

[code lang="CSharp"]public interface IServiceLocator : IServiceProvider
{
object GetInstance(Type serviceType);
object GetInstance(Type serviceType, string key);
IEnumerable

TService GetInstance();
TService GetInstance(string key);
IEnumerable GetAllInstances();
}[/code]

This is a very simple interface, representing the most common use cases for retrieving dependencies. To place a concrete container behind the interface, an adapter class is written to implement the interface, using the actual container as a backend. CSL includes adapters for a number of popular IoC containers, and it is not a hard task to write a custom adapter.

Common Service Locator includes a static class which provides access to the current IServiceLocator implementation. The public interface is as follows:

[code lang="CSharp"]public static class ServiceLocator
{
public static IServiceLocator Current { ... }
public static void SetLocatorProvider(ServiceLocatorProvider newProvider) { ... }
}[/code]

There is an access point to get the “current” container implementation, which can then be used according to the IServiceLocator interface. There is also a means to set the current implementation, usually done during the application startup routine. That process instantiates the appropriate container, does the necessary configuration, and passes it to the ServiceLocator class.

Remember that it is preferable to keep direct usage of the container, whether wrapped by the CSL or not, to a minimum. This can be aided by only referencing it where necessary – surely not every class needs to retrieve registered dependencies. One technique is the use of specially-purposed factory classes, which can construct specific objects, in turn delegating to the container where needed.

If, at this point, you are having to use the IoC container, via the CSL, in certain (hopefully limited) places to retrieve dependencies, then all well and good. You do take on some coupling to the CSL, but by the very act of implementing DI to support the use of an IoC container, you’ve already made huge gains towards the goal of reducing code coupling. Now, using the CSL keeps the process easier for you when adding/changing code, or when creating objects to use. That’s a huge win.

For a final point on the CSL, I’ll reiterate that it accommodates the most common functionality found in IoC containers: resolving dependencies. There is no facility for configuring the container, or handling registration. The reasoning behind this decision is that the configuration and registration steps greatly differ between containers, and are unimportant for the purpose of the CSL project. That’s a reasonable choice, but there may well be a need to have a common interface that offers such additional functionality; Davy Brion’s Agatha project does this.

For a related project, there is Siege.ServiceLocation. The author, Marcus Bratton, recently wrote a post on the idea behind using Siege on top of IoC frameworks.

Observations

Again, as done in the previous articles, I will gather bullet points to summarize this article.

  • Service location and dependency injection can be effective in reducing coupling and encouraging abstraction, but they require manual work in code to get the maximum benefit, especially when creating object graphs.
  • Inversion of control is not so much a separate technique as is it a superset of DIDI is a form of IoC, after all.
  • IoC brings the same benefits of the previous techniques: less coupling, more abstraction, and better testability as a result.
  • Implementing IoC ensures a well-decoupled codebase, easing future changes.
  • An IoC tool is not required, but can automate creation of objects and object graphs, reducing the amount of code required. There is some added complexity when including an IoC container in the mix.

Conclusion

It is time to wrap up this series. It took longer than I anticipated, but there was much research, experimenting, and learning involved along the way. Hopefully I’ve gathered and distilled enough information to be useful.

I started off with an explanation of code coupling, and why it is desirable to keep such coupling low for a number of reasons, changeability and testability in particular. I then outlined the steps of the process towards better code. This was followed by the later articles, culminating here and now.

My explanation of service location showed how a central registry can be used to store and retrieve common dependencies. This technique is a basic one, and brings some new problems, but it does still help towards reducing coupling, thereby increasing testability.

Dependency injection goes further than service location in reducing coupling. DI does not inherently depend on any tool or component, and is done purely in code. It works by specifying that a class has certain dependencies, without which it may not be used.

Inversion of control is not so much another whole technique as it is a culmination of the previous ones. It builds on top of DI to connect high-level components with their lower-level dependencies.

It’s worth noting that the use of an IoC container is not required, although it does simplify the creation of object graphs. Graph creation can still be done via the [hybrid] poor man’s technique; it may take a little more code, but it’s also one less component to deal with. Even without a container, you can still write code to implement IoC.

Overall, full IoC – with a container – is likely more suitable than basic service location for large projects; which is used matters less than the practice of separating service definitions from their implementations, and object creation from use. Both techniques are means to an end: better code.

Extra Reading

IoC can be a tricky subject to comprehend; I found it helpful to read different tutorials and discussions on the subject, with different example and perspectives. And so I in turn provide some additional reading material should they be necessary!