Inversion of Control Using Dependency Inversion Principle

Dependency Inversion Principle in C#

In the last article, we discussed implementing Inversion of Control (IoC) principle using Factory design pattern and we achieved the first level of loosely coupled design. Here, in this article, I will discuss how to implement Inversion of Control using Dependency Inversion Principle (DIP) as the second step to achieve the loosely coupled design.

Inversion of Control flow diagram

First of all, let’s understand what is Dependency Inversion Principle (DIP)?

The DIP (Dependency Inversion Principle) is one of the SOLID principles. Please click here to learn the SOLID Principles in detail.

The Dependency Inversion Principle states that

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

We need to keep the High-level module and Low-level module as loosely couple as much as possible.

When a class knows about the design and implementation details of another class, then it raises the risk that if we do any changes to one class will break the other class too. So we must keep these high-level and low-level modules/class loosely coupled as much as possible. To do that, we need to make both of them dependent on abstractions instead of knowing each other.

To understand DIP, we are going to work with the same example that we started in our previous article. So please read our previous article before proceeding to this article where we discussed how to implement Factory Design Pattern in C#.

We created the following classes in our previous article.

Employee.cs
namespace IoC_DIP_DI_Demo
{
    public class Employee
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Department { get; set; }
        public int Salary { get; set; }
    }
}

EmployeeDataAccess.cs

namespace IoC_DIP_DI_Demo
{
    public class EmployeeDataAccess
    {
        public Employee GetEmployeeDetails(int id)
        {
            // In real-time get the employee details from db
            //but here we are hard coded the employee details
            Employee emp = new Employee()
            {
                ID = id,
                Name = "Pranaya",
                Department = "IT",
                Salary = 10000
            };
            return emp;
        }
    }
}

DataAccessFactory.cs

namespace IoC_DIP_DI_Demo
{
    public class DataAccessFactory
    {
        public static EmployeeDataAccess GetEmployeeDataAccessObj()
        {
            return new EmployeeDataAccess();
        }
    }	
}

EmployeeBusinessLogic.cs

namespace IoC_DIP_DI_Demo
{
    public class EmployeeBusinessLogic
    {
        EmployeeDataAccess _EmployeeDataAccess;

        public EmployeeBusinessLogic()
        {
            _EmployeeDataAccess = DataAccessFactory.GetEmployeeDataAccessObj();
        }

        public Employee GetEmployeeDetails(int id)
        {
            return _EmployeeDataAccess.GetEmployeeDetails(id);
        }
    }
}

In the above example, we implemented the factory design pattern to achieve IoC. But the EmployeeBusinessLogic class uses the concrete EmployeeDataAccess class (EmployeeDataAccess _EmployeeDataAccess). So still our design is tightly coupled even though we have inverted the dependent object creation logic to the DataAccessFactory class.

Let’s use Dependency Inversion Principle on the EmployeeBusinessLogic and EmployeeDataAccess classes and make them more loosely coupled.

As per DIP definition, “a high-level module should not depend on low-level modules. Both should depend on an abstraction”.

So, first, we need to understand which one is the high-level module (class) and which one is the low-level module (class).

A high-level module is a module which depends on other modules.

In our example, EmployeeBusinessLogic class depends on EmployeeDataAccess class, so here, the EmployeeBusinessLogic class is the high-level module or class and the EmployeeDataAccess class is the low-level module or class.

So, as per the first rule of DIP, the EmployeeBusinessLogic class should not depend on concrete EmployeeDataAccess class, instead, both classes should depend on abstraction.

The second rule in the DIP is “Abstractions should not depend on details. Details should depend on abstractions”.

What is Abstraction?

Abstraction means something which is non-concrete.

In programming terms, Abstraction is one of the most important features of object-oriented programming (OOP) languages. Its main goal is to handle complexity by hiding unnecessary details and providing the necessary details to call the object operations. 

In the above example, EmployeeBusinessLogic and EmployeeDataAccess classes are concrete classes, meaning we can create objects of them.

So, abstraction in programming means we need to create either an interface or abstract class which is non-concrete means we should not create an instance.

As per DIP, EmployeeBusinessLogic (high-level module) class should not depend on concrete EmployeeDataAccess (low-level module) class.

Both classes should depend on abstractions, meaning both classes should depend on either the interface or the abstract class.

What should be in the interface (or in the abstract class)?

As you can see, the EmployeeBusinessLogic class uses GetEmployeeDetails() method of EmployeeDataAccess class. In real time, there will be many employee related methods or operations in EmployeeDataAccess class. So, let’s declare GetEmployeeDetails(int id) method in the interface as shown below.

Add one interface with the name IDataAccess and copy and paste the below codes.

IDataAccess.cs

namespace IoC_DIP_DI_Demo
{
    public interface IEmployeeDataAccess
    {
        Employee GetEmployeeDetails(int id);
    }
}

Now, implement IEmployeeDataAccess in EmployeeDataAccess class as shown below.

Modify the EmployeeDataAccess class as shown below

EmployeeDataAccess.cs

namespace IoC_DIP_DI_Demo
{
    public class EmployeeDataAccess : IEmployeeDataAccess
    {
        public Employee GetEmployeeDetails(int id)
        {
            // In real time get the employee details from db
            // but here we are hard coded the employee details
            Employee emp = new Employee()
            {
                ID = id,
                Name = "Pranaya",
                Department = "IT",
                Salary = 10000
            };
            return emp;
        }
    }
}

Now, modify our DataAccessFactory class which will return IEmployeeDataAccess instead of concrete EmployeeDataAccess class as shown below.

DataAccessFactory.cs

namespace IoC_DIP_DI_Demo
{
    public class DataAccessFactory
    {
        public static IEmployeeDataAccess GetEmployeeDataAccessObj()
        {
            return new EmployeeDataAccess();
        }
    }
}

Now, modify the EmployeeBusinessLogic class which will use IEmployeeDataAccess instead of concrete EmployeeDataAccess class as shown below.

EmployeeBusinessLogic.cs
namespace IoC_DIP_DI_Demo
{
    public class EmployeeBusinessLogic
    {
        IEmployeeDataAccess _EmployeeDataAccess;

        public EmployeeBusinessLogic()
        {
            _EmployeeDataAccess = DataAccessFactory.GetEmployeeDataAccessObj();
        }

        public Employee GetEmployeeDetails(int id)
        {
            return _EmployeeDataAccess.GetEmployeeDetails(id);
        }
    }
}

That’s it. We have implemented Dependency Inversion Principle (DIP) in our example where high-level module (i.e. EmployeeBusinessLogic class) and low-level module (i.e. EmployeeDataAccess class) now depend on abstraction (i.e. IEmployeeDataAccess interface).

Also, abstraction (i.e. IEmployeeDataAccess interface) does not depend on details (i.e. EmployeeDataAccess class) but details depend on abstraction.

Now run the application and see everything is working as expected.

OUTPUT:

Dependency Inversion Principle in C#

Advantages of implementing DIP in the above example is that EmployeeBusinessLogic and EmployeeDataAccess classes are loosely coupled classes because the EmployeeBusinessLogic class does not depend on concrete EmployeeDataAccess class, instead it uses reference of IEmployeeDataAccess interface. So now, we can easily use another class which implements IEmployeeDataAccess with a different implementation.

Still, we have not achieved fully loosely coupled classes because EmployeeBusinessLogic class includes DataAccessFactory class to get the reference of IEmployeeDataAccess. This is where the Dependency Injection pattern helps us.

In the next article, I will discuss how to use Dependency Injection design pattern using the above example to achieve the loosely coupled design.

SUMMARY

In this article, I try to explain the Dependency Inversion Principle in C# to achieve loosely coupled classes with an example. I hope this article will help you with your need. I would like to have your feedback. Please post your feedback, question, or comments about this article.

Leave a Reply

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