Sign in

Dependency Inversion Principle Explained

Understanding the “D” in “SOLID”

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.

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 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.