Dependency Inversion Principle

In this article, I am going to discuss the Dependency Inversion Principle with a real-time example. The Letter D in SOLID stands for the Dependency Inversion Principle which is also known as DIP.

Here is the list of the blogs in this series:

  1. Understand SOLID Principle
  2. Single Responsibility Principle
  3. Open Close Principle
  4. Liskov Substitution Principle
  5. Interface Segregation Principle

What is the Dependency Inversion Principle?

The Dependency Inversion Principle (DIP) states that

  1. High-level modules/classes should not depend on low-level modules/classes. Both should depend upon abstractions.
  2. Abstractions should not depend upon details. Details should depend upon abstractions.

The most important point that you need to remember while developing real-time applications, always try to keep the High-level module and Low-level module as loosely coupled as possible.

When a class knows about the design and implementation of another class, it raises the risk that if we do any changes to one class will break the other class. So we must keep these high-level and low-level modules/classes loosely coupled as much as possible. To do that, we need to make both of them dependent on abstractions instead of knowing each other.

Let us understand the Dependency Inversion Principle with one example

Let’s get started with some code that violates that principle.

Say you are working as part of a software team. We need to implement a project. For now, the software team consists of:

A BackEnd Developer:

public class BackEndDeveloper {    public void writeJava() { }
}

And a FrontEnd developer:

public class FrontEndDeveloper {    public void writeJavascript() { }
}

And our project uses both throughout the development process:

public class Project {    private BackEndDeveloper backEndDeveloper = new BackEndDeveloper();

private FrontEndDeveloper frontEndDeveloper = new FrontEndDeveloper();

public void implement() {
backEndDeveloper.writeJava(); frontEndDeveloper.writeJavascript();
}
}

So as we can see,

  1. The Project class is a high-level module, and it depends on low-level modules such as BackEndDeveloper and FrontEndDeveloper. We are actually violating the first part of the dependency inversion principle.
  2. Also, by inspecting the implement function of Project.class, we realize that the methods writeJava and writeJavascript are methods bound to the corresponding classes. Regarding the project scope, those are details since, in both cases, they are forms of development. Thus, the second part of the dependency inversion principle is violated.

In order to tackle this problem, we shall implement an interface called the Developer interface:

public interface Developer {
void develop();
}

Therefore, we introduce an abstraction.

The BackEndDeveloper shall be refactored to:

public class BackEndDeveloper implements Developer {    @Override
public void develop() {
writeJava();
}
private void writeJava() { }}

And the FrontEndDeveloper shall be refactored to:

public class FrontEndDeveloper implements Developer {    @Override
public void develop() {
writeJavascript();
}
public void writeJavascript() {}
}

The next step, in order to tackle the violation of the first part, would refactor the Project class so that it will not depend on the FrontEndDeveloper and the BackendDeveloper classes.

public class Project {

private List<Developer> developers;

public Project(List<Developer> developers) {
this.developers = developers;
}

public void implement() {
developers.forEach(d->d.develop());
}
}

The outcome is that the Project class does not depend on lower level modules, but rather abstractions. Also, low-level modules and their details depend on abstractions.

Thanks for reading this article ❤

If I got something wrong? Let me in the comments. I would love to improve.

Clap 👏 If this article helps you.

Follow me on Twitter.

📱 Android Engineer, 📝 Writer, 💻 Open Source Contributor, Techie, IoT, Interactive Projects, ☁ AWS, Google Cloud, Firebase, Python, React.