Despite the terms Dependency Inversion Principle (DIP), Inversion of Control (IoC), Dependency Injection (DI) and Service Locator existing for many years now, developers are often still confused about their meaning and use. When discussing software decoupling, many developers view the terms as interchangeable or synonymous, which is hardly the case. Aside from misunderstandings when discussing software decoupling with colleagues, this misinterpretation of terms can lead to confusion when tasked with designing and implementing a decoupled software solution. Hence, it’s important to differentiate between what is principle, pattern and technique.
Dependency Inversion Principle
A common misunderstanding made by many developers is thinking that the DIP and DI are one and the same. We’ll visit DI in detail later, but for now, let’s review the DIP as originally postulated by Robert C. Martin. The principle states:
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.
In a nutshell, this principle states that instead of having high-level and low-level components, or classes, tightly bound to each other, there should be an abstraction, or standard interface, which the high-level component will define and the actual implementation (detail) of the low-level component will be dependent.
To best illustrate why this principle of Dependency Inversion has merit, we’ll consider a set of classes which need to be instantiated in a particular order. For this example, we have a “GeoLocationProcessor” consumer class which instantiates the “GeoLocationProvider” class. The GeoLocationProvider class contains the implementation, or detail, required for obtaining data associated with interfacing with a downstream Geocoding service. With the GeoLocationProcessor class instantiating the GeoLocationProcessor class, we have the standard object graph in which the higher-level object is dependent on and tightly coupled to the lower-level, or implementation, object.
However, while this scenario creates the necessary object graph, it’s now impossible to use the GeoLocationProcessor without bringing along the GeoLocationProvider —which could prove to be problematic when attempting to adhere to proper unit testing technique. One solution to eliminate the tight coupling may be to create an interface, “IGeoLocationProvider,” which the higher-level class, GeoLocationProcessor, will be dependent upon and the lower-level class, GeoLocationProvider, will implement.
In this scenario, the interface allows the implementation details to be decoupled from the higher-level class. However, despite the implementation being decoupled, the dependency has not yet been inverted because the interface is still owned by the lower-level class. If there is a change in the GeoLocationProvider which impacts IGeoLocationProvider, then the GeoLocationProcessor will also need to be changed since it depends on the interface. Hence, the higher-level component is still ultimately dependent on the lower-level class. In order to completely achieve dependency inversion, it is important to understand that the abstracted component, or the interface in this case, must be “owned” by the higher-level class.
In this scenario the GeoLocationProcessor will define the interface, but the GeoLocationProvider will provide the detail by implementing it. With this repackaging of the interface, it is now clear that the detail provided by the GeoLocationProvider is dependent on the policy defined by GeoLocationProcessor. Hence, with the dependency now completely inverted, any implementation changes in the GeoLocationProvider will in no way impact the GeoLocationProcessor. If there are changes in the GeoLocationProcessor which impact the IGeoLocationProvider interface, the GeoLocationProvider will in-turn need to be modified.
This aligns with the DIP since the details are to be dependent upon the abstractions. Maintaining the DIP allows software solutions to be loosely coupled, independent, modular and more testable. For example, using the preceding scenario, if there was a need to create a new GeoLocationProvider utilizing a different downstream Geocoding service, all that would be required is to create another concrete class that implements the IGeoLocationProvider interface.
public interface IGeoLocationProvider { Location GetLatitudeLongitude(Address address); } public class GeoLocationProviderA: IGeoLocationProvider { public Location GetLatitudeLongitude(Address address){/*...*/} } public class GeoLocationProviderB: IGeoLocationProvider { public Location GetLatitudeLongitude(Address address) {/*...*/} }
Since there’s only a need to create a new GeoLocationProvider, there is no impact to the higher-level class. This allows the software solution to be modular and adaptive to change. This modularity aids in unit testing because it’s now possible to exercise the individual components thorough the use of mocks eliminating any chaining of modules. While the DIP has many benefits, it should be noted that it is a principle and does not dictate how to solve dependency problems. In order to gain insight on actually solving the dependency problems, we’ll move on to the next term that often confuses developers — IoC.
Inversion of Control
Although it’s often confused with the DIP, IoC is different in that it is a programming technique that allows run-time object coupling binding to occur by a framework where the binding is otherwise not known at compile time using static analysis. In contrast to the DIP that concentrates on high and low-level components being dependent on abstractions, IoC speaks more to the control technique utilized to provide the abstraction. Since in the DIP, the lower-level class is no longer instantiated by its higher-level object, a programming technique is required to instantiate the lower-level class. IoC provides this technique.
In order to better explain IoC, it’s helpful to compare the flow control between traditional console and Windows applications. Historically, normal flow is quite procedural in nature. In a typical command line application, the application will request a piece of information followed by another request of some nature in a very step-by-step fashion.
In this situation, the code is in control. The code makes the decision when to request data from the user, when to read the response and how to process the information. Hence, it’s a procedural flow. An alternative approach for obtaining the same information might be to employ the use of a graphical user interface (GUI) application.
In contrast to the command line application, we now have text box controls for the user to enter information. Now the user may enter the requested information in any particular order. Of course, the big difference between the two approaches is the basic flow of control. Depending on the bindings setup for the individual text box controls, the underlying framework will now control when certain methods are called to possibly perform validation or actually read user input. In this way, the control is inverted. This paradox is IoC, also known to some as the Hollywood Principle: “Don’t call us, we’ll call you.”
This IoC phenomenon can be applied for the creation of objects during run-time. It is quite normal to instantiate classes from within another class in the following manner:
public class GeoLocationProcessor { GeoLocationProviderA geoLocationProviderA = new GeoLocationProviderA(); }
While this is normal instantiation, it can tend to lead to a tightly coupled design depending on the application specifics. A slight alternative might be to utilize an interface.
public class GeoLocationProcessor { IGeoLocationProvider geoLocationProviderA = new GeoLocationProviderA(); }
The use of an interface might be a small improvement. However, the object is still being instantiated within the class which leads to tight coupling with little to no room for modularity. The optimal solution is to move the creation of the GeoLocationProviderA class outside of the GeoLocationProcessor class. One option for creating the GeoLocationProviderA class would be to utilize a factory class.
public class Creator { public IGeoLocationProvider FactoryMethod(string provider) { switch (provider) { case "A": return new GeoLocationProviderA(); break; case "B": return new GeoLocationProviderB(); break; default: return new GeoLocationProviderDefault(); break; } } }
Using the factory class, the creation of the GeoLocationProvider object has now been inverted. There is no longer a need to modify the GeoLocationProcessor class when the need for a new GeoLocationProvider is identified. The tight coupling between the classes has been eliminated and the modularity of the application has been improved. While the factory class, or pattern, is one type of creation inversion, there are others including DI and the Service Locator Pattern. However, it should be noted that IoC is the technique for inverting the control of object creation, and not the actual pattern for making it happen.
Dependency Injection
Frequently, developers confuse IoC with DI. As mentioned previously, IoC deals with object creation being inverted, whereas DI is a pattern for supplying the dependencies since the control is inverted. The basic concept of DI is to utilize an external object from the class being instantiated that will populate the dependencies within that class using the appropriate implementation for a specified interface.
There are three main types of DI: Constructor Injection, Setter Injection and Interface Injection.
Constructor Injection
In Constructor Injection, the dependencies are passed to the class being instantiated through its constructor. The injection of the dependencies are most often handled by an injector object known as a “container,” but any higher-level object could also inject the dependencies.
For example, in the code below, the GeoLocationProcessor has a dependency on the object that implements the IGeoLocationProvider. This dependency will be passed through its constructor.
public class GeoLocationProcessor { private readonly IGeoLocationProvider _geoLocationProvider; public Address Address { get; set; } public GeoLocationProcessor(IGeoLocationProvider geoLocationProvider) { this._geoLocationProvider = geoLocationProvider; } public Location GetCoordinates(Address address) { return this._geoLocationProvider.GetLatitudeLongitude(Address); } }
The code calling the GeoLocationProcessor would need to create the dependency and then provide it when instantiating the GeoLocationProcessor as shown below.
// Create the dependency IGeoLocationProvider geoLocationProvider = new GeoLocationProviderA(); // Instantiate GeoLocationProcessor providing the dependency var geoLocationProcessor = new GeoLocationProcessor(geoLocationProvider);
As shown earlier, GeoLocationProviderB also implements the IGeoLocationProvider interface. Thus, GeoLocationProviderB could be swapped with GeoLocationProviderA above with no impact to the GeoLocationProcessor.
Because of its simple approach, Constructor Injection is the most widely used form of dependency injection. It’s very easy to see the dependencies, which is especially important for mandatory dependencies. Constructor Injection also has the benefit of enforcing the order of initialization, thus preventing circular dependencies. As long as the number of dependencies is not too large, it works well. Otherwise, as the number of dependencies grow the constructor can start to become unwieldy. It should also be avoided in scenarios where an object requires a large number of constructors that forward to one another.
Setter Injection
Though not as commonly used as Constructor Injection, Setter Injection passes dependencies through property setters instead of the constructor. In this scenario, there is no need for a constructor, only a property to set the dependency which occurs by the parent class.
public class GeoLocationProcessor { public IGeoLocationProvider GeoLocationProvider { get; set; } public Address Address { get; set; } public Location GetCoordinates(Address address) { return this.GeoLocationProvider.GetLatitudeLongitude(Address); } }
Then the dependency is injected through the publicly exposed setter.
// Create the dependency IGeoLocationProvider geoLocationProvider = new GeoLocationProviderA(); GeoLocationProcessor geoLocationProcessor = new GeoLocationProcessor(); // Passing dependency though setter geoLocationProcessor.GeoLocationProvider = geoLocationProvider; geoLocationProvider.GetLatitudeLongitude(new Address());
One advantage of Setter Injection is the object can be created without any dependencies and works well with optional and/or conditional dependencies. However, this could be an issue if the calling object attempts to execute a portion of the child object prior to the dependencies being set. For this reason, Setter Injection should be avoided for mandatory dependencies.
Interface Injection
While not often used, a third type of DI is Interface Injection. An Interface is defined that contains a method responsible for setting dependencies.
// Create the dependency public interface IDependencyInjector public interface IDependencyInjector { // Method responsible for setting dependency void SetDependency(IGeoLocationProvider geoLocationProvider); }
The dependent class implements this interface.
// Create the dependency public class GeoLocationProcessor : IDependencyInjector { private IGeoLocationProvider _geoLocationProvider; public Address Address { get; set; } public void SetDependency(IGeoLocationProvider geoLocationProvider) { this._geoLocationProvider = geoLocationProvider; } public Location GetCoordinates(Address address) { return this._geoLocationProvider.GetLatitudeLongitude(Address); } }
After instantiating the dependent class, the consumer class will call the implemented interface to set the dependencies.
// Create the dependency IGeoLocationProvider geoLocationProvider = new GeoLocationProviderA(); GeoLocationProcessor geoLocationProcessor = new GeoLocationProcessor(); // Passing dependency ((IDependencyInjector)geoLocationProcessor).SetDependency(geoLocationProvider); geoLocationProvider.GetLatitudeLongitude(new Address());
Again, most likely due to the complexity involved, this method of DI is not frequently used. For applications with a large number of interfaces, Interface Injection can tend to become very involved because of problems getting the various components and dependencies sorted out.
The Service Locator Pattern
An alternative to the DI pattern that also supports IoC is the Service Locator pattern (or anti-pattern to some developers). This pattern can be controversial since some developers think it’s actually an anti-pattern. (The reasons for labeling it as an anti-pattern will be discussed shortly.) In contrast to DI, the Service Locator pattern relies on the dependent class to resolve its own dependencies via a container object or some other object that can return dependencies. This object is referred to as the “Service Locator.” Hence, there is a need for a class that can be utilized as the Service Locator. While the implementation of the Service Locator varies, often generics are utilized.
public static class ServiceLocator { private readonly static Dictionary<Type, object> Services = new Dictionary<Type, object>(); public static T GetService<T>() { return (T)ServiceLocator.Services[typeof(T)]; } public static void Register<T>(T service) { ServiceLocator.Services[typeof(T)] = service; } }
It is then up to the dependent class to resolve its own dependencies, usually from within its constructor.
public class GeoLocationProcessor { private readonly IGeoLocationProvider _geoLocationProvider; public Address Address { get; set; } public GeoLocationProcessor() { this._geoLocationProvider = ServiceLocator.GetService<IGeoLocationProvider>(); } public Location GetCoordinates(Address address) { return this._geoLocationProvider.GetLatitudeLongitude(Address); } }
In the sample code above, the static ServiceLocator class is referenced to get the required dependency. However, some implementations will pass in the whole service locator through the constructor. Of course, whichever method is used, the service locator object must be registered by the consuming class.
ServiceLocator.Register<IGeoLocationProvider>(new GeoLocationProviderA()); var geoLocationProcessor = new GeoLocationProcessor(); var address = new Address() {Address1 = "1 Main Street", City = "Anytown", State = "MD", Zip = "21700"}; var location = geoLocationProcessor.GetCoordinates(address);
As shown in the sample code, the Service Locator pattern has all the merits of Dependency Injection: it allows for modularity, aids in unit testing by allowing the switching of concrete classes and it allows for late binding.
However, some consider the Service Locator pattern to be an anti-pattern due to a major drawback: It hides dependencies that are required in the dependent class. In Constructor injection. it’s possible to view the dependencies of the class from the outside (i.e., API signatures). Using Service Locator, the dependencies are hidden within the constructor body. Therefore, it’s quite possible that a run-time error could occur because the dependencies were not properly registered prior to the dependent class being instantiated. Also, if dependencies are added to the constructor at a later time, it is possible they will go unnoticed and again result in a run-time error. Despite these drawbacks, the Service Locator is still a valid pattern regarding IoC. However, Constructor Injection is favored in most situations.
Using StructureMap as a Dependency Injection/Inversion of Control Tool
While it’s possible to create your own DI/IoC framework, there are several frameworks readily available. One such widely used framework is StructureMap. The StructureMap DI/IoC tool has gained a lot of acceptance in the .NET development space as a very dependable framework for creating loosely coupled and highly testable applications.
StructureMap can be downloaded from the StructureMap website or added directly to projects via NuGet. After right-clicking on the project references and selecting “Manage Nuget Packages,” the Nuget Extension Manager will display. Then, search for StructureMap.
After installing StructureMap, it will display in the project references. The following namespace is required to use StructureMap.
using StructureMap;
For this example, we’ll use the GeoLocationProcessor class and inject the dependencies via its constructor.
public class GeoLocationProcessor { private readonly IGeoLocationProvider _geoLocationProvider; public Address Address { get; set; } public GeoLocationProcessor(IGeoLocationProvider geoLocationProvider) { this._geoLocationProvider = geoLocationProvider; } public Location GetCoordinates(Address address) { return this._geoLocationProvider.GetLatitudeLongitude(Address); } }
Since the GeoLocationProcessor has a dependency on a class that implements IGeoLocationProvider, we’ll utilize the GeoLocationProviderA and GeoLocationProviderB classes.
public class GeoLocationProviderA: IGeoLocationProvider { public Location GetLatitudeLongitude(Address address) { return new Location(){Latitude = 40.0755, Longitude = -76.329999}; } } public class GeoLocationProviderB: IGeoLocationProvider { public Location GetLatitudeLongitude(Address address) { return new Location() { Latitude = 39.415741, Longitude = -77.412033 }; } }
With the dependent classes defined, the next task is to register the dependencies with the StructureMap container.
static void Main(string[] args) { // Instantiate the container and register the dependency IContainer container = ConfigureDependencies(); // Instantiate the consuming class var geoLocationProcessor = container.GetInstance<GeoLocationProcessor>(); var address = new Address() { Address1 = "1 Main Street", City = "Anytown", State = "MD", Zip = "21700" }; var location = geoLocationProcessor.GetCoordinates(address); Console.WriteLine("The geographical coordinates are {0}, {1}.", location.Latitude, location.Longitude ); Console.ReadLine(); } private static IContainer ConfigureDependencies() { return new Container(x => x.For<IGeoLocationProvider>().Use<GeoLocationProviderA>()); }
Once the dependency is registered, the consuming class must be instantiated and then the method call to GetCoordinates(address) is made.
By modifying the container registration to use GeoLocationProviderB as the concrete class for IGeoLocationProvider, it can be shown that different output will result.
private static IContainer ConfigureDependencies() { return new Container(x => x.For<IGeoLocationProvider>().Use<GeoLocationProviderB>()); }
In this example, it’s easy to see how IoC/DI using StructureMap promotes a loosely coupled and modular design. If GeoLocationProviderA and GeoLocationProviderB were coded to call two different Geocoding services, it would be easy to swap the dependencies in the event a different service was required. This same concept can be applied when unit testing. The IGeoLocationProvider interface can be easily mocked thus promoting effective unit testing principles.
Summary
The preceding examples and diagrams have hopefully illustrated the key differences in concept and mechanics between the DIP, the IoC technique, the DI pattern and the Service Locator pattern. It’s important to not only understand how certain IoC/DI frameworks, such as StructureMap, are used, but also why they are used and how they differ.