Dependency Inversion Principle Explained
What is Dependency Inversion Principle?
The Dependency Inversion principle is the last of the five SOLID design principles described by Robert C. Martin, which are principles that encourage us to create more understandable, maintainable and flexible software.
The principle of Dependency Inversion states that:
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend upon details. Details should depend upon abtractions.
This principle refers to the decoupling of software modules so that instead of high-level modules depending on low-level modules, both will depend on abstractions.
Why is Dependency Inversion Principle Important?
The goal is to avoid tight coupling of modules which can easily break the application. Low-level modules can include classes related to data or utilities, whereas high-level modules encapsulate those with more complex logic.
How can Dependency Inversion Principle be applied?
Lets look at an example of the relationship between a Windows 98 computer that comes with a monitor and a standard keyboard.
The code implementation is shown below. This implementation works as we are able to use the StandardKayboard
and Monitor
in our Windows98Computer
class.
public class Windows98Machine {
private final StandardKeyboard keyboard;
private final Monitor monitor;
public Windows98Machine() {
monitor = new Monitor();
keyboard = new StandardKeyboard();
}
}
However, this also result in the three classes being tightly coupled as we declare the StandardKeyboard
and Monitor
classes with the new
keyword. This does not only make the Windows98Computer
difficult to test, we are also not able to switch out the standard keyboard with a different type of keyboard, and the same goes for the monitor.
To resolve this issue, we could decouple our computer from the StandardKeyboard
by adding a general Keyboard
interface that can be implemented by the computer class as shown below.
The code implementation is shown below.
public interface Keyboard { }public class StandardKeyboard implements Keyboard { }public class Windows98Machine {
private final Keyboard keyboard;
private final Monitor monitor;public Windows98Machine(Keyboard keyboard, Monitor monitor) {
this.keyboard = keyboard;
this.monitor = monitor;
}
}
Here, we have used the dependency injection pattern to facilitate adding the Keyboard
dependency into the Winows98Machine
. We also modified the StandardKeyboard
class to implement this interface so that it can be injected into the Windows98Machine
. The same principle can be applied to the Monitor
class as well. Our classes are now decoupled and can communicate through the Keyboard
abstraction. If there is another type of keyboard, we can easily switch out the keyboard in our machine with a different implementation of the interface.
Conclusion
The dependency injection principle remains an important principle in software engineering to avoid tight coupling that could lead to breaks in our application. This is achieved by creating an abstract layer for the low level modules so that high level modules can depend on the abstraction instead of concrete implementation.