Dependency Inversion Principle (DIP)

Dependency Inversion Principle (DIP) is the last principle among the SOLID principles of Object Oriented Programming (OOP) model that we have seen in the last article. Let’s recall what Dependency Inversion Principle (DIP) talks about as follows:

  • High-level modules should not depend on low-level modules and both should depend on abstraction.
  • Details or concrete implementation should depend on abstraction rather than an abstraction depends on details.

DIP is a design principle, which just provides a suggestion or guideline about what is the right and wrong way of designing the application. It doesn’t give a concrete solution. In short, DIP just talks on what to do instead of how to do.

In this article, we will discuss the Dependency Inversion Principle (DIP) in more detail.

Dependency Inversion Principle – High-level and Low-level modules

Before jumping into the main topic, let’s understand what is high-level and low-level modules. In the anatomy of software development methodology, we often build a large system which comprises of small, independent, discrete and self-contained components called modules.

Each module in the system is designed for specific functionality. It is capable of performing a task in a self-sufficient manner. These modules are basic building blocks of the entire application. This is what we called modularization.

Above all, the module comprises of various elements. These elements work in a combined manner to form a module as a single unit. The elements of modules work together and the module itself may interact with other modules to execute the task that span across multiple modules.

Cohesion and Coupling

This design brings the two most important concepts in the software design paradigm as follows.

  • Cohesion: It’s a measurement of interaction happens between the elements within the module.
  • Coupling: It’s an amount of interdependency between the modules to achieve a specific task.

The cohesion can be correlated with the amount of responsibility in a real scenario. It’s an amount of obligation towards the specific task that we have identified for a module to act upon.

A module must be crafted with high cohesion design. High cohesion means the module is well focused on a specific task for which it’s designed for. It makes the module fine-grained, well dedicated, robust and reusable. High cohesion can be labeled in a single sentence as “Do one thing only but do it well”

Coupling, on the other hand, is a level of dependency a module has on other modules. In a modular application, a module is designed for specific functionality.

Obviously, it has to rely on other modules to perform a task for which it’s not designed. This is where the coupling comes into the picture. You might have got the clue that, to make the system less fragile, you must reduce and manage the coupling in a proper way.

When modules are interdependent to each other, one should make sure the changes introduced in one module should not break the others. In short, the coupling should be defined with a well-established interface between the modules to reduce the ripple impact of changes.

The Dependency Inversion Principle moves around these two concepts.

Modules executing a core part of the application are called high-level module. The high-level modules rely on other modules to perform supporting functions. They are called low-level modules.

Changes are part of the software development cycle. If not applied in a proper manner, they produce maintenance nightmare down the line. Changes become risky, especially with dependent code.

Anatomy of Dependency Inversion Principle

This is what DIP talks about – keep the chunk of dependent code (low-level module) away from the main program (high-level module) which is not directly associated.

In other words, DIP suggests eliminating the direct dependency of the low-level module on high-level modules to reduce the coupling. Rather, they must rely on some sort of abstraction so that the high-level module can define a generic low-level behavior.

DIP helps in avoiding the ripple effect of implementation changes happen to the low-level module on the high-level module. This ultimately brings flexibility and adaptivity into the application. As far as the low-level module is aligned with the abstraction, the high-level module can work with it without any code change.

Dependency Inversion Principle with example

Let’s have look at a real-life example and see how DIP can significantly improve the overall application’s flexibility.

Consider a scenario where you are designing a BI (Business Intelligence) system for a departmental store. The purpose of this application is to gather, analyze, incorporate, and present business information in various format.

Typically, you need to fetch data from the database, process it with complex logic and show it on the screen. If this is implemented with a procedural development style, the flow of the system would be similar to the following diagram.

Dependency Inversion Principle - BI system with procedural way

A single module does all the job – fetching data from DB, process it with business logic, and export it on screen. This is not an optimal design.

Modular design

Let’s break the whole functionality into multiple modules based on their responsibility as follow:

Dependency Inversion Principle - BI system with modular architecture

  • Import DB data module: Responsible for obtaining data from the relational database.
  • Export HTML module: Export the processed data into HTML format.
  • Data Analysis module: Taking data from the import data module, process it with complex business rules and deliver it to export data module.

Considering the responsibility, the Data analysis module is a high-level module while Import Data and Export Data are low-level modules.

The Import DB data module should look similar to the following code snippet.

The Export HTML module will take the list of data and export it to HTML format. The code should look as follows:

The Data Analysis module takes the data from the Import DB data module, applies the business logic and send the processed data to the Export HTML module. Its code looks as follows:

Further improvement

Cool ..!!! This seems good design as we have separated out the module based on their responsibility. However, good design can sustain any future changes without breaking the system.

Is this design capable of handling the changes? Will this design make our application fragile while accommodating any amendments?

Let’s find the possibilities of changes in low-level modules. Assume that, over a period of time, you might need to fetch the data from Web service along with the Database. Similarly, you need to export the data in Excel format along with HTML.

To accommodate the new requirement, you need to create new classes/ modules to fetch data from web service and export into Excel sheet format as follows:

Let’s integrate the new low-level modules to high-level modules. But at this time, since we have multiple import and export data modules, we need some sort of flag mechanism. Based on the vale of the flag we pass, the instance of low-level modules will be created inside the high-level module. The updated code of Data Analysis module looks as follows:

Resolve design problem with Dependency Inversion Principle

Great work ..!! Now the high-level module is flexible enough to deal with two different input and output methods to generate the data analysis report.

But hold on. What happens when few more input and output methods are introduced? Let say you need to fetch the data from Google drive and export it into PDF format.

For every new low-level module, we need to keep updating the code in the high-level module. This is because the high-level module is closely dependent on the concrete implementation of the low-level module.

In other words, the high-level and low-level modules are tightly coupled. This breaks another fundamental design principle – Open for extension and close for modification.

Now, it’s time to apply the Dependency Inversion Principle. Let’s recall what DIP says: High-level module should not depend on low-level modules for their responsibilities. Both should depend on abstraction.

This was the core problem in our design. The high-level module (Data analysis) is tightly coupled with low-level modules (import and export data).

Limitation of Dependency Inversion Principle

Unfortunately, Design principles talk about the solution to the given problem. They remain silent about how to implement it. DIP suggests to eliminate the coupling between the high level and low-level modules but doesn’t show how to implement it.

Dependency Inversion Principle - The limitation of DIP

This is where the Inversion Of Control (IOC) comes into the picture. IOC reveals the way to define the abstraction between the modules to reduce the coupling. In short, IOC defines the way to implement the Dependency Inversion Principle (DIP).

We will see IOC in more detail in the next article. Meanwhile, you can find a few more examples where you can apply the DIP.

Summing Up

Dependency Inversion Principle (DIP) suggests how to set the dependency between the modules. However, it doesn’t reveal the actual implementation details. In short, DIP can be summarized as follows:

  • Break the whole system in independent modules with high cohesion design.
  • Identify the modules in a high-level and low-level category.
  • Design the dependency in a way that a high-level module should not depend on low-level modules. 
  • Since DIP is a design principle, it only talks about what to do and not how to do. For that, you need to rely on design patterns and methodologies.
  • In the next article, we will see Inversion of Principle (IOC), which shows a way to implement DIP – the How part.

Recommended For You

About the Author: Nilang

Nilang Patel is a technology evangelist who loves to spread knowledge and helping people in all possible ways. He is an author of two technical books - Java 9 Dependency and Spring 5.0 Projects.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.