DIP: The Dependency-Inversion Principle

Fifth and last of a series of blog posts about the SOLID principles

Gabriella’s Journey
4 min readDec 6, 2017

In this post I’m going to write about the last principle of the SOLID principles which I’ve previously written about: the Dependency-Inversion Principle (or simply DIP.)

I’m currently reading Robert Martin’s book Clean Code: A Handbook of Agile Software Craftsmanship. I normally read it on the tube while commuting to/from my office, and this morning I was reading chapter 10 about Classes and how they should be organised in terms of dependency and changes-efficiency. Funnily enough, towards the end of the chapter ‘Uncle Bob’ mentions the DIP itself (or maybe I shouldn’t be that surprised about it…)

In Clean Code, ‘Uncle Bob’ defines the DIP this way:

In essence, the DIP says that our classes should depend upon abstractions, not on concrete details.

In his Agile Software Development: Principles, Patterns, and Practices (or PPP) ‘Uncle Bob’ describes the principle in more details:

a. High-level modules should not depend on low-level modules. Both should depend on abstractions.

b. Abstractions should not depend on details. Details should depend on abstractions.

In a software application we can consider the low-level modules the classes which implement basic and primary operations and which contain details, and high-level modules the classes which encapsulate complex logic.

When high-level modules depend on low-level modules, changes to the lower level modules can have direct effects on the higher level modules which may have to be changed in turn. Besides, high-level modules that depend on low-level modules become hard to reuse, whereas it should be the contrary (we want high-level modules to be able to reuse in different contexts.)

A solution to avoid direct dependency

The best way to avoid undesired coupling between classes of different level hierarchies is by using abstractions. Each of the classes in the different levels should use an abstract interface implemented by the next-lowest class.

This way the upper-level classes do not depend on the lower-level classes directly but it’s the lower-level classes instead that depend on abstract interfaces declared in, and called by, the upper-level classes.

If the principle is applied, high-level classes are not affected by any changes to low-level classes. In addition, high-level classes can be reused in any context that defines lower-level classes that implement an abstract interface defined in upper-level classes.

Thanks to the DIP, we can create an application with a more flexible, durable and mobile design.

An example

I’m going to use the ‘Button’ example from ‘Uncle Bob’’s PPP, as it was quite simple and clear.

We start from two classes: Button class, which receives a poll() message that determines whether or not a user has pressed it. And a Lamp class, which receives a turnOn() message and a turnOff() message:

Button      ----->    Lamp
poll() turnOn()
turnOff()

Button class implementation:

public class Button() {   private Lamp lamp;   public void poll() {
if (something) {
lamp.turnOn();
} else {
lamp.turnOff();
}
}
}

Button class depends directly on Lamp class: if Lamp changes, then Button will have to be changed too.

Furthermore, Button class is not reusable: you can’t use it to switch on a washing machine, for example.

The above code is a violation of the DIP. To put it in ‘Uncle Bob’’s words:

- The high-level policy of the application has not been separated from the low-level implementation.

- The abstractions have not been separated from the details.

- The high-level policy automatically depends on the low-level modules, and the abstractions automatically depend on the details.

To solve the design issue behind the code above, we need to create a middle-layer where to define an abstract interface associated withButton and implemented by any classes like Lamp:

                (interface)Button  ----->  ButtonServer    <-----  Lamp
poll() turnOn() <----- WashingMachine
turnOff() <----- TimeMachine

Above we can see how the dependency was ‘inverted’: Button no longer depends on a concrete class like Lamp but it is associated to an abstract interface that defines turnOn() and turnOff(). Classes like Lamp (or any other class that would use a Button) now depends on an interface which they implement its methods of.

The design above allows a Button to control any device that is willing to implement the ButtonServer interface. It also means that Button will be able to control objects that have not yet been invented, and this makes the application more flexible.

Conclusion

As for all the SOLID principles, the DIP is just a guide-line, a piece of advice meant to improve the design and the functionality of an application. But, as for all other principles, it should not be applied blindly.

An experienced and skilled developer should be able to recognise when and which of the good-design principles should be applied and if it’s worth using it in terms of costs and efforts when writing an application.

--

--