loading...
Cover image for What inverts in the dependency inversion principle?

What inverts in the dependency inversion principle?

ruidfigueiredo profile image Rui Figueiredo Updated on ・3 min read

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();
        }
    }
}

Diagram of copier class depending on concrete classes keyboard and printer
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();
        }
    }
} 

Diagram of copier class obeying the dependency inversion principle
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.

Posted on by:

ruidfigueiredo profile

Rui Figueiredo

@ruidfigueiredo

Currently working as a contractor, mostly on Node.js and Typescript, also React. Also have a background in academia, I have a PhD in CS and worked as a researcher in AI.

Discussion

pic
Editor guide
 

Could it be this:

Before: Copier will break if keyboard renames the Read method.
After: Keyboard will break if IReader interface changes (on behalf of the Copier's needs).

Dependencies are inverted.

 

That was a good read.

I think we can say that before the inversion, high level module depends on low level modules coz the copier should know about the keyboard and printer modules in order for it to call their functions, and the low level modules may know nothing about the high level ones.

After the inversion, the high level modules introduces a protocol, a contract aka an interface that the low level modules should know about in order to implement them, that is the inversion as the high level modukes now may/should know nothing about the low level ones