Dependency Inversion Principle

Principle

High-level classes shouldn't have to change because low-level classes changed.

First, let's address what are high-level and low-level classes. Let's use a driving analogy. If you were designing a car, the high-level classes would be the parts drivers interact with most: steering wheel, accelerator, brake pedal, etc. These components are the first point of entry to the system (kind of like a user interface). The low-level classes would be the components that high-level classes interact with (e.g., tires, engine, brakes) to do their job (fulfill their behavior). What happens to the high-level classes if you change the engine from gas to electric? Ideally, nothing! A driver still steers, accelerates, and brakes using the same functionality.

Next, let's see an example in code. Suppose you have the following partial class diagram as part of a course management system.

The (high-level) Gradebook class depends on (the low-level) SQLiteDatabase for reading and persisting its data. This means, any changes to the low-level class (SQLiteDatabase) may affect the high-level class Gradebook, which isn't supposed to care about the data storage details.

A common fix is to introduce an interface as follows:

As a bonus, this design also allows us to extend (add or replace) the persistence mechanism without modifying the high-level class (hence, we also applied the Open/Closed Principle).

Notice in the "fix," both the high-level and the low-level classes depend on the interface (an abstraction). The dependency inversion principle does not just change the direction of the dependency; it splits the dependency between the high-level and low-level modules by introducing an abstraction between them. So in the end, you get two dependencies:

  • The high-level module depends on the abstraction, and
  • The low-level module depends on the same abstraction.

Principle restated!

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

Case in point

It is common (and to some extent, natural) that you start developing a software application from the low-level classes (develop the model, persistence, ... before you get to high-level classes like those that comprise the user interface). Therefore, it is imminent to end up with a system where high-level components depend on low-level ones. This principle suggests changing the direction of this dependency, as soon as it arises.