You are here

SOLID Principles-The Dependency Inversion Principle

SOLID Principles-The Dependency Inversion PrincipleAlmost everywhere when we talk about delivery of a product, the first step comes in mind is it’s design. The more focus we put into the design, the better the product will look. Every design has some design principles that need to be followed while designing a product.  Hence, design principles have a crucial role in any product delivery. Design Principles help teams with decision making. In this article, We will discuss about ‘SOLID Principles-The Dependency Inversion Principle’.

A few simple principles or valuable questions can guide our team towards taking relevant decisions. SOLID Principles are the set of five principles used to design a software. In fact, the word ‘SOLID’ is the acronym for the set of five principles that contains the first letter of each principle. Let’s discuss SOLID Principles-The Dependency Inversion Principle in detail and related concepts.

What is SOLID Principles-The Dependency Inversion Principle (DIP) ?

The Dependency Inversion Principle (DIP) states that high-level modules should not depend upon low-level modules; they should depend on abstractions. Secondly, abstractions should not depend upon details; details should depend upon abstractions.

This way, instead of high-level modules depending on low-level modules, both will depend on abstractions. Every dependency in the design should target an interface or an abstract class. No dependency should target a concrete class.

The idea is that we isolate our class behind a boundary formed by the abstractions it depends on. If all the details behind those abstractions change, then our class is still safe. This helps keep coupling low and makes our design easier to change. DIP also offers us to test things in isolation, details like database are plugins to our system.

Robert Martin equated the Dependency Inversion Principle, as a first-class combination of the Open Closed Principle and the Liskov Substitution Principle.

Example: Code that violates Dependency Inversion Principle

Suppose a book store asked us to build a new feature that enables customers to put their favorite books on a shelf.

In order to implement the new functionality, we create a lower-level Book class and a higher-level Shelf class. The Book class will allow users to see reviews and read a sample of each book they store on their shelves. The Shelf class will let them add a book to their shelf and customize the shelf. For example, observe the below code.

public class Book {

    void seeReviews() {
         ...
    }

    void readSample() {
         ...
    }
}


public class Shelf {

     Book book;

     void addBook(Book book) {
          ...
     }

     void customizeShelf() {
          ...
     }
}

Everything looks fine, but as the high-level Shelf class depends on the low-level Book, the above code violates the Dependency Inversion Principle. This becomes clear when the store asks us to enable customers to add DVDs to their shelves, too. In order to fulfil the demand, we create a new DVD class:

public class DVD {

     void seeReviews() {
          ...
     }

     void watchSample() {
          ...
     }
}

Now, we should modify the Shelf class so that it can accept DVDs, too. However, this would clearly break the Open/Closed Principle too.

Example: Code that follows Dependency Inversion Principle

The solution is to create an abstraction layer for the lower-level classes (Book and DVD). We’ll do so by introducing the Product interface, both classes will implement it. For example, below code demonstrates the concept.

public interface Product {

    void seeReviews();

    void getSample();

}

public class Book implements Product {

    @Override
    public void seeReviews() { 
          ...
    }

    @Override
    public void getSample() {
          ...
    }
}

public class DVD implements Product {

    @Override
    public void seeReviews() { 
         ...
    }

    @Override  
    public void getSample() {
          ...
    }
}

Now, Shelf can reference the Product interface instead of its implementations (Book and DVD). The refactored code also allows us to later introduce new product types (for instance, Magazine) that customers can put on their shelves, too.

public class Shelf {

    Product product;

    void addProduct(Product product) {
          ...
    }

    void customizeShelf() {
          ...
    }
}


The above code also follows the Liskov Substitution Principle, as the Product type can be substituted with both of its subtypes (Book and DVD) without breaking the program. At the same time, we have also implemented the Dependency Inversion Principle, as in the refactored code, high-level classes don’t depend on low-level classes, either. Let’s check with the class diagram in both the cases.

 

Interface Segregation Principle Violation

 

SOLID Principles : The Interface Segregation Principle

Other Example

Here is another example : A program depends on Reader and Writer interfaces that are abstractions, and Keyboard and Printer are details that depend on those abstractions by implementing those interfaces. Here CharCopier is oblivious to the low-level details of Reader and Writer implementations and thus you can pass in any Device that implements the Reader and Writer interface and CharCopier would still work correctly.

public interface Reader {

    char getChar();
}

public interface Writer {

    void putChar(char c);
}

public class CharCopier {

     void copy(Reader reader, Writer writer) {

     int c;

     while ((c = reader.getChar()) != EOF) {

          writer.putChar();

     }
  }
}


public Keyboard implements Reader{
       ...
}

public Printer implements Writer{ 
       ...
}


How is DIP related to Dependency Injection of Spring Framework?

It would be correct if you think that Dependency Inversion Principle is related to Dependency Injection as it applies to the Spring Framework. Uncle Bob Martin introduced the concept of Dependency Inversion before Martin Fowler introduced the term Dependency Injection. These both concepts are extremely related. Dependency Inversion is more concentrated on the structure of your code. Moreover, its focus is keeping your code loosely coupled. On the other hand, Dependency Injection is about how the code functionally works.

Dependency Inversion Principle has been very well implemented in Spring framework, the beauty of this design principle is that any class which is injected by DI framework is easy to test with the mock object and easier to maintain because object creation code is centralized in the framework and client code is not messed up with that.

What is the benefit of Dependency Inversion Principle?

Below are some of the benefits when we apply this principle in our code.

1) Keeps your code loosely coupled
2) Easier Maintenance
3) Better Code Reusability

What is the disadvantage of Dependency Inversion Principle?

When we use this principle, it results in an increased effort. We need to maintain more classes and interfaces, in a few words more complex code, but more flexible. If someone applies this principle blindly for every class or every module, it may not provide any benefit.

When should we not use Dependency Inversion Principle?

If our java class has functionality that is more likely to remain unchanged in the future, there is no need to apply this principle as it will not provide us any benefit.

FAQ

What is the difference between high-level modules and low-level modules in DIP?

High-level modules generally represent the components or modules responsible for higher-level business logic, while low-level modules handle lower-level details or specific implementations. DIP encourages these modules to interact through abstractions.

Can you provide an example of how DIP can be applied in code?

Consider a class that sends email notifications. Instead of directly creating and using a specific email service implementation, you can create an abstraction (e.g., an EmailService interface) and have the class depend on that abstraction. This way, you can switch email service implementations easily.

Can you provide a real-world example of how DIP has been used successfully in a software project?

One common example is a web application framework like Spring in Java. Spring relies heavily on DIP by using dependency injection to manage and inject dependencies into components, offering developers to switch implementations easily.

What are abstractions in the context of DIP?

Abstractions are interfaces, abstract classes, or any other code that define a contract or API without specifying the implementation details. They allow high-level modules to interact with low-level modules through a common interface.

Does DIP recommend avoiding concrete implementations completely?

No, DIP doesn’t suggest avoiding concrete implementations. It only suggests that high-level modules should depend on abstractions, and those abstractions may have concrete implementations. This allows flexibility in choosing specific implementations.

How does DIP relate to Inversion of Control (IoC) and Dependency Injection (DI)?

DIP is closely related to IoC and DI patterns. IoC refers to the inversion of control over the flow of a program, where control is moved from the program to a container. DI is a technique that implements IoC by injecting dependencies (often abstractions) into a class rather than letting the class create its own dependencies.

Are there tools or frameworks that assist in implementing DIP in software projects? 

Yes, there are dependency injection frameworks in various programming languages (e.g., Spring in Java, Angular in TypeScript) that help manage dependencies and facilitate the application of DIP principles.

How does DIP relate to other SOLID principles like Single Responsibility Principle (SRP) and Interface Segregation Principle (ISP)?

DIP complements other SOLID principles. It helps follow the SRP by reducing the chance of a class having multiple reasons to change. It also aligns with ISP by favoring smaller, more focused abstractions.

That’s all about ‘SOLID Principles-The Dependency Inversion Principle’. Moreover, Wikipedia defines this Principle like this. In order to learn the most commonly used design principles, kindly visit the article on ‘OOPs Design Principles‘. Apart from ‘SOLID Principles : The The Dependency Inversion Principle’, we have discussed other principles as separate articles respectively.

Links to Other Design Principles

Single Responsibility Principle

Open Closed Principle(OCP)

Liskov’s Substitution Principle(LSP)

Interface Segregation Principle(ISP)

2 thoughts on “SOLID Principles-The Dependency Inversion Principle

  1. Thanks for writing such awesome articles on the SOLID Design Principle. I loved reading all of them. The definition is explained to the point in a very simple way. Code examples are good as well. Hat’s off to the writer @devs5003

Leave a Reply


Top