Inversion of Control Using Dependency Inversion Principle

Inversion of Control using Dependency Inversion Principle in C#

In this article, I am going to discuss the Inversion of Control using Dependency Inversion Principle in C# with an example. Please read our previous article where we discussed implementing the Inversion of Control (IoC) using the Factory design pattern and we achieved the first level of loosely coupled design. Here, in this article, I will show you how to implement Inversion of Control using the Dependency Inversion Principle (DIP) as the second step to achieve the loosely coupled design.

Inversion of Control using Dependency Inversion Principle in C#

What is the 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/classes loosely coupled as much as possible. To do that, we need to make both of them dependent on abstractions instead of knowing each other.

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 the 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).

Hight Level and Low-Level Modules in Software Design:

A high-level module is a module that 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 the 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 the GetEmployeeDetails() method of EmployeeDataAccess class. In real-time, there will be many employee-related methods or operations in the 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 the Dependency Inversion Principle (DIP) in our example where the high-level modules (i.e. EmployeeBusinessLogic class) and low-level modules (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.

Dependency Inversion Principle in C#

The advantage 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 the reference of IEmployeeDataAccess interface. So now, we can easily use another class that implements IEmployeeDataAccess with a different implementation.

Still, we have not achieved fully loosely coupled classes because the 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 am going to discuss how to use the Dependency Injection design pattern using the above example to achieve the loosely coupled design. Here, In this article, I try to explain the Inversion of Control using Dependency Inversion Principle in C# with an example. I hope this article will help you with your needs. 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 *