Blog for Junior Developers C#/.NET

Wednesday, July 03, 2024

It's time to decipher the last, but definitely not the least important letter of SOLID, i.e. D for Dependency Inversion Principle.

This principle says that:
-High-level modules should not depend on low-level modules. Both should depend on abstraction.
-Abstractions should not depend on details. It is the details that should depend on the abstraction.

The systems we write should address abstractions (through the use of interfaces or abstract classes), not concrete elements. By following the principle of dependency inversion, we minimize dependencies (loose coupling) in the application. This will make our applications more flexible and make changes easier in the future.

solid-dip-everything-you-should-know-about-the-dependency-injection-principle.jpg

SOLID - DIP - Everything You Should Know About the Dependency Inversion Principle (DIP)


First of all, the DIP principle says that we should reduce dependencies to specific implementations. It is best to achieve this by relying on interfaces, then we have small dependencies in the code. Interfaces are stable. That is, if we make some change in an interface, this change is also associated with a change in the implementations of this interface. However, if we make a change in a specific implementation, we usually do not need to change our interface. As a result, interfaces are more stable than implementations. Additionally, by writing code based on interfaces, it becomes more testable. You can easily write unit tests for such code, because you can substitute a fake implementation (mock) for the interface.


Code that does not follow the dependency inversion rule:


public class Employee
{
    public string Name { get; set; }
}

public class EmployeeRepository
{
    public void Add(Employee employee)
    {
        //Add employee to database
    }
}

public class EmployeeService
{
    private EmployeeRepository _employeeRepository = new EmployeeRepository();

    public void Add(Employee employee)
    {
        _employeeRepository.Add(employee);
    }
} 

Let's first define the basics, i.e. what are high-level modules and what are low-level modules, without this you will not understand the principle of dependency inversion. In the above code, the high-level module is the EmployeeService class and the low-level module is the EmployeeRepository class. The high-level module in this example depends on the low-level module because it uses a specific implementation, the EmployeeRepository class, and this violates the DIP principle. To make this code compatible with DIP, we need to reverse these dependencies (as the name says). What's the best way to do this? An implementation where the dependencies are reversed may look, for example, as in the example below.


Code that follows the dependency inversion principle:


public class Employee
{
    public string Name { get; set; }
}

public interface IEmployeeRepository
{
    void Add(Employee employee);
}

public class EmployeeRepository : IEmployeeRepository
{
    public void Add(Employee employee)
    {
        //Add employee to database
    }
}

public class EmployeeService
{
    private IEmployeeRepository _employeeRepository;

    public EmployeeService(IEmployeeRepository employeeRepository)
    {
        _employeeRepository = employeeRepository;
    }

    public void Add(Employee employee)
    {
        _employeeRepository.Add(employee);
    }
} 

In the above example, our EmployeeService no longer depends on a specific EmployeeRepository implementation, but only on an abstraction. In this case, from the IEmployeeRepository interface. Changes to a low-level module do not affect the high-level module. In this way, it was possible to reverse the dependencies. Now, according to the Dependency Inversion Principle, the high-level module no longer depends on the low-level module, but depends on the abstraction. A high-level module is not interested in changes that will be made to a low-level module. The only important thing is that it implements the above interface.


Introduction to Dependency Injection (DI)


The EmployeeService class accepts the IEmployeeRepository interface in its constructor. You can create an instance of a class like this:

public class Program
{
    static void Main()
    {
        var employeeService = new EmployeeService(new EmployeeRepository());
    }
} 


However, a better solution is to also create an interface for IEmployeeService and instantiate new classes by using the Dependency Injection container. By using the Dependecy Injection container, after proper configuration, you can operate on interfaces in your application and you will be less likely to create objects via "new". I will try to write about what Dependency Injection is and how to implement Dependency Injection in one of my future blog articles.

dependency-inversion-principle-device.jpg

SUMMARY


The main goal of using the Dependency Inversion Principle is to separate the dependencies of high-level modules from low-level ones by using abstractions. This principle says that we should rely on interfaces that make our applications have fewer dependencies. Of course, we should not always rely entirely on interfaces, but we should do it wisely and use it in places where it makes sense. It is best to use Dependency Injection (DI) containers to apply DIP.

Author of the article:
Kazimierz Szpin

KAZIMIERZ SZPIN
Software Developer C#/.NET, Freelancer. Specializes in ASP.NET Core, ASP.NET MVC, ASP.NET Web API, Blazor, WPF and Windows Forms.
Author of the blog CodeWithKazik.com

Previous article - SOLID - ISP - Everything You Should Know About the Interface Segregation Principle
Next article - Programming Compliant with SOLID Rules - A Guide for Beginner Programmers
Comments (1)
Kazimierz Szpin
KAZIMIERZ SZPIN, czwartek, 11 lipca 2024 11:08
What do you think about this article? I'm curious about your opinion.
Dodaj komentarz

Search engine

© Copyright 2024 CodeWithKazik.com. All rights reserved. Privacy policy.
Design by Code With Kazik