Dependency Inversion Principle: SOLID Design Principles

Share the Article

What is Dependency Inversion Principle

Dependency Inversion principle is one of the most important principle for object oriented application designing. It is also contributing fifth letter “D” in SOLID acronym where D stand for Dependency Inversion. This principle addresses future modifiability and maintainability issues of an application. Main idea of this principle is to introduce loose coupling between the classes inside an application so that future enhancement is very easy.

“Dependency Inversion” principle try to design classes such that high level classes should not depend on low-level classes directly. If high-level modules become directly dependent on lower-level modules then replacement of lower-level module becomes very difficult . If any updates in lower level we need to update high level also . Due to this class maintainability will be very tough and time consuming. Plus, testability will also impact. For each change in low-level part, we need to update test cases for high-level parts also.

We should design high-level module and low-level module such that they should not directly interact with each other. Instead of this, we should have an abstraction layer, which take care of interaction. Interaction between level should depend on abstraction layer, so that we can very easily replace low layer module.

How

Lets us understand the need of “Dependency inversion” principle by taking an example of application of School.

When not using Dependency Inversion Principle

Below application is depicting class School where we are setting games and number of player needed for a game .Finally, we are showing details about all the games present in a School.

#include<iostream> #include<map> #include<string> using namespace std; class Cricket { int m_player; public: Cricket(int player) { m_player = player; } int getPlayer() { return m_player; } }; class Badminton { int m_player; public: Badminton(int player) { m_player = player; } int getPlayer() { return m_player; } }; class School { map<string,int> m_game; public: void addCricket(Cricket &c1) { int t = c1.getPlayer(); m_game.insert (std::pair<string,int> ("Cricket",t)); } void addBadminton(Badminton &b1) { int t = b1.getPlayer(); m_game.insert(std::pair<string,int> ("Badminton",t)); } void showAllGame() { auto it = m_game.begin(); while(it!=m_game.end()) { cout<<it->first<< " " <<it->second<<endl; it++; } } }; int main() { School a1; Cricket c1(12); Badminton b1(2); a1.addCricket(c1); a1.addBadminton(b1); a1.showAllGame(); return 0; }

As seen above, we are supporting Cricket and Badminton games .

Below are the details of application:

  • School is the high-level module.
  • Cricket and Badminton are low-level modules.
  • School is directly interacting with Cricket and Badminton

Suppose, now we have to update above application for future need as More games such as Hockey, Swimming are introduced.

Below is the updated design of above application:

class School { map<string,int> m_game; public: void addCricket(Cricket &c1) { int t = c1.getPlayer(); m_game.insert(std::pair<string,int> ("Cricket",t)); } void addBadminton(Badminton &b1) { int t = b1.getPlayer(); m_game.insert(std::pair<string,int> ("Badminton",t)); } void addHockey (Hockey h1) { } void showAllGame() { auto it = m_game.begin(); while(it!=m_game.end()) { cout<<it->first<< " " <<it->second<<endl; it++; } } };

As seen above, high-level class School is very tightly coupled with low level classes. Therefore, any update is directly impacting School class.

We are violating “Dependency Inversion” principle. As per “Dependency Inversion” we should have very loose coupling between layers. Hence, we are seeing side-effects also due to violation of “Dependency Inversion” in terms of maintainability efforts.

How

Let’s update above School with “Dependency Inversion” principle.

Thumb rule – High-level should not directly interact with low-level and we should have abstraction between them.

First create abstract class for Low-Level Classes

First create abstract class Game for and Cricket and Badminton. This class shall reside in School class.

class Game { }; class Cricket: public Game { }; class Badminton: public Game { }; Class School { Game g1; };

Create relevant methods in abstract class

Basically, we require loose coupling between high and low level. Hence create appropriate interface for interacting between all layers.

class Game { public: void showGames(); }; class Cricket: public Game { }; class Badminton: public Game { }; Class School { Game g1; public: void showGames() { gl.showGames(); } };

Below is the class diagram:

Dependency inversion principle UML diagram. SOLID design principles

Same example now using Dependency Inversion Principle

Below is the updated application:

#include<iostream> #include<map> #include<string> using namespace std; class Game { static map<string,int> m_game; public: void showGames() { auto it = m_game.begin(); while(it != m_game.end()) { cout<<it->first<< " " <<it->second<<endl; it++; } } }; map<string,int> Game::m_game; class Cricket: public Game { int m_player; public: Cricket(int player) { m_player = player; Game::m_game.insert(std::pair<string,int> ("Cricket",m_player)); } int getPlayer() { return m_player; } }; class Badminton: public Game { int m_player; public: Badminton(int player) { m_player = player; Game::m_game.insert(std::pair<string,int> ("Badminton",m_player)); } int getPlayer() { return m_player; } }; class School { Game m_g; public: void showAllGame() { m_g.showGames(); } }; int main() { School a1; Cricket c1(12); Badminton b1(2); a1.showAllGame(); return 0; }

As seen above, now we have abstraction layer “Game” between high-level School and low-level Cricket and Badminton modules. In future, if we have to add more features or new games then we just need to update abstraction layer. Finally, if we have to add new game, just derive a new class from Game class. No change in School class.

Pros & Cons of Dependency Inversion Principle

Pros

  • Future addition of module is very easy due to loose coupling
  • Changes are very less so less impact on Unit test

Cons

  • Complex due to addition of one more abstraction layer

Main Funda: Dependency Inversion Principle ensures that there is loose-coupling between different logical levels.

Advanced C++ Topics

Single Responsibility Principle
Open Closed Principle
Liskov’s Substitution Principle
Interface Segregation Principle

Share the Article

Leave a Reply

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