Have you ever wondered why the dependency inversion principle is named the way it is?
The dependency inversion principle is often described as “Depend on abstractions, not on concretions.”. But from that description it is hard to make up what is inverted.
If we would draw a class diagram of a class structure where the dependency inversion principle was not present and then we changed it so that it would not violate the principle, would the arrows indicating dependencies invert? Well, let’s try and see.
I’m shamelessly basing this example from the one from here, taken from Uncle Bob’s butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod.
Imagine you have a Copier
class, and that class takes input from a keyboard and sends it to a printer. The Copier
class depends on a Keyboard
class to read keystrokes from the user and a Printer
class to print those keys (Fig 1).
public class Copier{
private readonly Keyboard _keyboard;
private readonly Printer _printer;
public Copier(Keyboard keyboard, Printer printer){
_keyboard = keyboard;
_printer = printer;
}
public void Copy(){
int c = _keyboard.Read();
while(!_keyboard.IsEndingCharacter(c)){
_printer.Write(c);
c = _keyboard.Read();
}
}
}
Fig 1 – Copier
class that violates the dependency inversion principle
And now we are going to apply the dependency inversion principle, i.e., instead of depending on concrete classes, Copier
will depend on abstractions (Fig 2). Those abstractions will be:
- IReader – defines a contract for getting input from a source
- IWriter – defines a contract to writing data to an output
public class Copier{
private readonly IReader _reader;
private readonly IWriter _writer;
public Copier(IReader reader, IWriter writer){
_reader = reader;
_writer = writer;
}
public void Copy(){
int c = _reader.Read();
while(!_reader.IsEndingCharacter(c)){
_writer.Write(c);
c = _reader.Read();
}
}
}
Fig 2 – Copier
class that does not violate the dependency inversion principle
Now, Keyboard
and Printer
are implementations of Reader
and Writer
, and Copier
does not depend on them any more, meaning you can now copy from and to other things, for example, from the keyboard to a file on disk.
There are many advantages in doing this. Now, Copier
can be reused without requiring the Keyboard
and Printer
classes. Changes in implementation details of Keyboard and Printer are guaranteed to not affect Copier
if the contract (method signature) that IReader
and IWriter
define doesn’t change.
But, do you see any clearly inverted dependencies between the diagrams?
Me neither.
But let’s try to describe the relationship between the Copier
class and it’s dependencies.
At first, the Copier
class depends on the low-level Keyboard
and Printer
classes.
After, the Copier
class depends on the high-level Reader
and Writer
. It is this invert from low to high that can be though of as the “inversion” part of dependency inversion.
It turns out that a long time ago higher level modules depending on lower level modules was something that was considered good practice (before Object Oriented Design). The name “Dependency Inversion” is alluding to this.
Here it is, more eloquently put, directly from the source (section: The Dependency Inversion Principle):
One might question why I use the word “inversion”. Frankly, it is because more traditional software development methods, such as Structured Analysis and Design, tend to create software structures in which high level modules depend upon low level modules, and in which abstractions depend upon details. Indeed one of the goals of these methods is to define the subprogram hierarchy that describes how the high level modules make calls to the low level modules … Thus, the dependency structure of a well designed object oriented program is “inverted” with respect to the dependency structure that normally results from traditional procedural methods.