Dependency Injection Vs Dependency Inversion Vs Inversion of Control, Let’s set the Record Straight
Dependency Injection, Dependency Inversion, and Inversion of Control are 3 terms that, although related, are commonly confused and misinterpreted. In the Internet wildlands you can find multiple articles and blog posts that depict them as being the same, or relate them in an incorrect manner, causing confusion and ambiguity to replicate endlessly.
At SSENSE we strictly follow proper design principles and patterns, this guarantees that our code has a level of future-proof extensibility and quality that meets our long-term ambitions and supports our hyper-growth.
In this article, I will attempt to set the record straight, give you some history, and make sense of why those misconceptions exist for these 3 important concepts.
Inversion of Control (IoC)
Inversion of control is a programming principle that inverts the flow of control in an application. In traditional procedural programming, the code that controls the execution of the program — the main function — instantiates objects, calls methods and even asks the user for input so that the execution can continue and the program can achieve its task. With IoC, it is a framework that does the instantiation, method calls and triggers user actions, having full control of the flow and removing this responsibility from the main function, and by consequence the application.
The concept of Inversion of Control was first introduced in 1988 by Ralph E. Johnson and Brian Foote in their Designing Reusable Classes paper, where they state:
One important characteristic of a framework is that the methods defined by the user to tailor the framework will often be called from within the framework itself, rather than from the user’s application code. The framework often plays the role of the main program in coordinating and sequencing application activity. This inversion of control gives frameworks the power to serve as extensible skeletons. The methods supplied by the user tailor the generic algorithms defined in the framework for a particular application.
Then, in 1996, Michael Mattson’s uses the IoC term in his thesis “Object Oriented Frameworks: a survey on methodological issues” to differentiate a real framework from a class library by stating:
The major difference between an object-oriented framework and a class library is that the framework calls the application code. Normally the application code calls the class library. This inversion of control is sometimes named the Hollywood principle, “Do not call us, we call You”.
Finally, in 1998, with the proposal of the Java Apache Server Framework or Avalon, Stefano Mazzocchi used Inversion of Control as one of the framework’s main driving design principles, popularizing the concept.
In the context of service containers, IoC is achieved by allowing the framework to do the binding and instantiation of dependencies. Instead of the application having to create and use services, it is the framework that determines the moment when the instantiation is needed, using a pre-set configuration that instructs it on how to execute these tasks.
Dependency Injection (DI)
Dependency Injection is a software design technique in which the creation and binding of dependencies are done outside of the dependent class. Afterwards, said dependencies are provided already instantiated and ready to be used, hence the term “injection”; in contrast to the dependent class having to instantiate its dependencies internally, and having to know how to configure them, thus causing coupling.
If you found the previous paragraph redundant to the paragraph preceding it, it is not by accident. Dependency Injection was the name coined by Martin Fowler in 2004 to have a better, and more specific name for this style, as opposed to the overly generic term Inversion of Control used by many frameworks.
DI can be achieved in 3 ways:
- Constructor Injection: When dependencies are provided through the dependent class’ constructor
- Interface Injection: When dependencies are provided directly into a dependent class method as an argument
- Setter Injection: When dependencies are provided via a public property of the dependent class
Regardless of what kind of injection type is used, this pattern separates the construction and configuration of services from its usage, relieving the dependent class from the former responsibility, decoupling it from its dependencies, and improving reusability and ease to test.
Fun fact: Stefano Mazzocchi harshly disagreed with Martin Fowler’s idea of renaming Inversion of Control to Dependency Injection, stating that he “missed the point” and that “IoC is about enforcing isolation not about injecting dependencies”.
There are other patterns where dependencies can be configured, instantiated and provided to a class for its usage, for example the Service Locator Pattern, in which classes depend on a service locator instance that would wire and provide the dependencies on command. The difference is, again, dependency. The Service Locator Pattern forces all classes to know about the locator instance; contrary to this, DI does not require the client class to be dependent on the service container, dependencies simply appear ready to be used, effectively achieving inversion of control.
Dependency Inversion Principle (DIP)
The Dependency Inversion Principle was conceived by Robert Martin, a.k.a. Uncle Bob, initially in his paper OO Design Quality Metrics in 1994, and later on formally named and defined in his book Agile Software Development Principles, Patterns and Practices in 2002, where he described it using the following 2 points:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
Let’s attempt to translate this definition into something more practical.
A high-level module, in this context, is a class that depends on a service, therefore, it is the service’s client. It is the high-level module that contains the important policy decision and business logic of an application. The low-level module is the class that will provide certain functionality to its client, therefore the service.
If a client depends on the implementation of a service, then when the service’s interface is changed, by consequence the client must change (and also its tests). If we invert this dependency so that the high-level module determines the interface that the low-level module must implement, then the low-level module can change as long as its contract is always respected.
It’s important to note that there’s not only an inversion of the direction of the dependency, but also an inversion in the interface ownership.
The following dependency flow violates the DIP since the high-level modules depend on the low-level ones.
Instead, we should find the underlying abstractions, or in Uncle Bob’s words we should find:
The truths that do not vary when the details are changed. It is the system inside the system — it is the metaphor.
Let’s fix that by inverting the dependency and making the details depend on abstractions.
In this case, the dependency direction is inverted, interfaces are determined at a higher level and classes in their same level depend on them, therefore they depend on abstractions. Additionally, lower-level class implementations depend on the interfaces defined at a higher level, therefore details now depend on abstractions.
If both client and service depend on an abstraction, then you essentially have an agreement that, if respected, will allow for the following:
- The client will be agnostic of who the dependency is, and instead rely on what it does
- The dependency will be guaranteed to behave in a way that is determined by the high-level policy
- The client can be reused in other contexts safely, trusting that its dependencies will respect the contract
- The dependency can always be replaced by another implementation that implements the same contract
As you can see all three concepts are related and co-exist in the same context, building applications and leveraging frameworks, but regardless of their relations and similarities, it is important to know their details and what benefits drive their usage.
Now that we have a solid understanding of these concepts, let’s hit the keyboard and get things depicted in a clearer way. As Linus Torvalds once said:
Talk is Cheap, Show me the Code!
Let’s take a theoretical e-commerce system that has an Order Service class to handle application logic for placing orders. This class depends on a Repository to persist the placed orders in the database, in addition, the Repository depends on 2 other classes, a Mapper to serialize the order data and a MySQL Client to interact with the database. We’ll be using Typescript and a great IoC container called InversifyJS.
In snippet 1, we can see a purely procedural approach where the OrderService class instantiates and uses all the objects it needs to perform its responsibility, causing a hard dependency, or coupling, between the class and all of its dependencies.
* Notice how there is hard coupling between the OrderService and the MySQLOrderRepository, making it fragile and difficult to test. Let’s improve this by using an IoC container and constructor injection.
In snippet 2, we set up all dependencies of OrderService and MySQLOrderReporitory as services in our IoC container, where we bind the identifier to the class to be instantiated when injected.
Additionally, let’s add the constructor injections that will remove the instantiation of dependencies in both our classes.
* This is an improvement, but not yet ideal since our OrderService depends on an implementation, the MySQLOrderReporitory. And, the Repository depends on 2 other implementations, the MySQLClient and the OrderMapper, both violating the Dependency Inversion Principle.
Let’s improve this by inverting the dependencies. First, we define our abstractions.
Our container’s bindings need to be updated with new types.
Our classes’ dependencies need to be updated as well.
In the last snippet, we can see that, by replacing the dependency types from concrete classes to interfaces, both low and high-level modules now depend on abstractions, respecting the Dependency Inversion Principle.
After going through the theory and some examples, we could conclude with the following statement:
The Inversion of Control is a fundamental principle used by frameworks to invert the responsibilities of flow control in an application, while Dependency Injection is the pattern used to provide dependencies to an application’s class. And, for the class and its services to be properly decoupled, the Dependency Inversion Principle should be respected by both the client and the service, always depending on an abstraction.
Editorial reviews by Catherine Heim & Mario Bittencourt
Want to work with us? Click here to see all open positions at SSENSE!