SOLID Design Principles: The Dependency Inversion

Avinash Dhumal
4 min readOct 5, 2022

--

Introduction

In today’s article, I am going to talk about the last principle of the S.O.L.I.D. principle — Dependency Inversion (DI), and the purpose of implementing it with a real time example.

Before moving on to the dependency inversion principle, I encourage you to read my previous articles on Single Responsibility, Open Closed, Liskov Substitution and Interface Segregation principles.

What is the Dependency Inversion Principle?

When I started learning SOLID principles, I felt dependency inversion principle is the hardest one out of 5 principles and I believe you must have felt that too. So, let’s understand “THE TECHNICAL JARGON” about dependency inversion and then later I will help you to drill down to simplest fashion so that you understand easily.

Basically, dependency inversion states 2 parts:

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

Confusing ah? What are high level, low level modules, what are abstractions and what details stand for? Let’s elaborate more on this:

  1. Your high level modules (say your classes exposed to client) which provide complex logics should easily be replaceable or re-usable and unaffected by any changes done in your low level modules (another set of classes) which provides features.
  2. In order to decouple high level and low level modules, we have to introduce abstractions — let’s say interfaces.
  3. That means, high level and low level modules are now dependent on abstractions and the details are dependent on your abstraction.

Still not clear? Let’s take an example of the following to understand.

Here we got 3 tier architecture where,

  1. Your application layer is using a business layer object and calling GetData() function.
  2. Your business layer is using a data access layer object and calling GetData() function.
  3. And your data access layer is performing some CRUD operations in GetData() to fetch data from the database.

The above architecture looks perfect as it does its intended purpose, but do you realize that all your layers are now coupled together and you can’t replace any layer if you wish to as it requires code changes in other layers too. And that’s where the dependency inversion principles come into play.

Let’s use the above same example and implement the new logic using the Dependency Inversion principle.

The above class diagram now helps us to understand how we introduced dependency inversion principle. Let’s go one by one to understand in detail.

  1. Previously our each layer was tightly coupled. Now we have removed the coupling by introducing interfaces in each layer (i.e. business and data access layers).
  2. And each layer is now calling interfaces instead of direct classes which again shows decoupling.
  3. If you carefully evaluate the methods in application and business layers, we have constructors which are accepting interface objects. Here, we are performing dependency injection via constructor which will help to get the object of the other layer and then later we can use wherever we want within your class.

Now, let’s take a look at following code example to understand how it looks. Do read comments given in the code example to understand more.

using System;namespace SOLID.DI
{
// ABSTRACTION LAYER FOR ACCESSING DAL LAYER
public interface IDALCustomer
{
string GetData();
}
// LOW LEVEL MODULE IMPLEMENTATION USING ABSTRACTION (DETAILS)
public class DALCustomer : IDALCustomer
{
public string GetData()
{
return "Hey, This is Avinash Dhumal!";
}
}
// ABSTRACTION LAYER FOR ACCESSING BUSINESS LAYER
public interface IBLCustomer
{
string GetData();
}
// LOW LEVEL MODULE IMPLEMENTATION USING ABSTRACTION (DETAILS)
public class BLCustomer : IBLCustomer
{
public IDALCustomer _iDALCustomer { get; }
// DEPENDENCY INJECTION TO INTRODUCE DATA ACCESS LAYER VIA INTERFACE
public BLCustomer(IDALCustomer DALCustomer)
{
_iDALCustomer = DALCustomer;
}
public string GetData()
{
return _iDALCustomer.GetData();
}
}
// HIGH LEVEL MODULE ACCESSING LOW LEVEL MODULE USING ABSTRACTION (INTERFACE)
public class AppLayer
{
public IBLCustomer _iBLCustomer { get; }
// DEPENDENCY INJECTION TO INTRODUCE BUSINESS LAYER VIA INTERFACE
public AppLayer(IBLCustomer BLCustomer)
{
_iBLCustomer = BLCustomer;
}
public string GetData()
{
return _iBLCustomer.GetData();
}
}
public class Program
{
static void Main(string[] args)
{
// REUSABLE/REPLACEABLE WITH ANY CLASS WHICH IMPLEMENT THOSE INTERFACE
AppLayer appLayer = new AppLayer(new BLCustomer(new DALCustomer()));
Console.WriteLine(appLayer.GetData());
Console.ReadKey();
}
}
}

Give it a try and see what output it produces.

Conclusion

With the above example, we have now understood how to use the dependency inversion principle to solve the problems with the example.

If you enjoy the content I create and would like to show your appreciation, you can buy me a coffee!

--

--

Avinash Dhumal

13+ years of software architect, design, development, management, and support experience in Microsoft technologies using Azure & AWS Cloud services.