Dependency Inversion Principle (DIP)
In this tutorial, we are going to discuss Dependency Inversion Principle (DIP) in SOLID Principles. Before discussing this topic, keep in mind that Dependency Inversion and Dependency Injection are both different concepts. Most people get confused about it and consider both are the same. Now two key points are here to keep in mind about this principle
- High-level modules/classes should not depend on low-level modules/classes. Both should depend upon abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
The above lines state that if a high module or class depends more on low-level modules or classes, your code would have tight coupling, and if you try to change one class, it can break another class, which is risky at the production level.
So always try to make classes loosely coupled as much as possible, and you can achieve this through abstraction. The main motive of this principle is decoupling the dependencies so if class A changes the class B doesn’t need to care or know about the changes.
This definition of dependency inversion principle may not make any sense now, and terms like high-level modules, low-level modules, abstraction, and details are confusing.
As per the dependency inversion principle, a high-level class/module is something that is dependent on other low-level classes/modules, and a low-level class/module is something that is not dependent on other classes. Details are nothing but the class which is having code for getting actual data (Business logic).
Let’s stop theory and see an example. For example, let’s assume you have two options to make payments Debit card and Credit card.
package com.ashok.solid.principles.dip;
/**
*
* @author ashok.mariyala
*
*/
public class DebitCard {
public void doTransaction(int amount) {
System.out.println("Transaction done with DebitCard");
}
}
package com.ashok.solid.principles.dip;
/**
*
* @author ashok.mariyala
*
*/
public class CreditCard {
public void doTransaction(int amount) {
System.out.println("Transaction done with CreditCard");
}
}
With these two cards, you went to a shopping mall, purchased some orders, and decided to pay using CreditCard.
package com.ashok.solid.principles.dip;
/**
*
* @author ashok.mariyala
*
*/
public class ShoppingMall {
private DebitCard debitCard;
public ShoppingMall(DebitCard debitCard) {
this.debitCard = debitCard;
}
public void doPayment(Object order, int amount) {
debitCard.doTransaction(amount);
}
public static void main(String[] args) {
DebitCard debitCard = new DebitCard();
ShoppingMall shoppingMall = new ShoppingMall(debitCard);
shoppingMall.doPayment("Some order", 5000);
}
}
If you observe this is the wrong design of coding, now ShoppingMall class tightly coupled with DebitCard.
Now there is some error in your debit card, and the user wants to go with a Credit card, then this won’t be possible because ShoppingMall is tightly coupled with Debit Card.
We can do that, remove the Debit card from the constructor and inject CreditCard. This is not a good approach to writing code because to follow Dependency inversion principle (DIP). We need to design our application in such a way so that my shopping mall payment system should accept any type of Card (it shouldn’t care whether it is debit or credit card).
To simplify this designing principle further, I am creating an interface called Bankcards like below.
package com.ashok.solid.principles.dip;
/**
*
* @author ashok.mariyala
*
*/
public interface BankCard {
public void doTransaction(int amount);
}
package com.ashok.solid.principles.dip;
/**
*
* @author ashok.mariyala
*
*/
public class DebitCard implements BankCard {
@Override
public void doTransaction(int amount) {
System.out.println("Transaction done with DebitCard");
}
}
package com.ashok.solid.principles.dip;
/**
*
* @author ashok.mariyala
*
*/
public class CreditCard implements BankCard {
@Override
public void doTransaction(int amount) {
System.out.println("Transaction done with CreditCard");
}
}
package com.ashok.solid.principles.dip;
/**
*
* @author ashok.mariyala
*
*/
public class ShoppingMall {
private BankCard bankCard;
public ShoppingMall(BankCard bankCard) {
this.bankCard = bankCard;
}
public void doPayment(Object order, int amount) {
bankCard.doTransaction(amount);
}
public static void main(String[] args) {
BankCard bankCard = new CreditCard();
ShoppingMall shoppingMall = new ShoppingMall(bankCard);
shoppingMall.doPayment("Some order", 5000);
}
}
This code establishes that both the high-level and low-level modules depend on abstraction. If you observe a ShoppingMall, it is loosely coupled with BankCard, any type of card processes the payment without any impact, proving the Dependency Inversion Principle (DIP).