SOLID Principles In C# - Dependency Inversion Principle

Introduction

C# is an object-oriented programming language. These days whenever you talk about object-oriented programming, you hear the acronym, SOLID. These are five design principles introduced by Michael Feathers to make our object-oriented applications easy to understand, maintain, and expand as future requirements change. Today, we will look at the fifth and final principle with an example. I covered the first four principles in my previous articles.

The SOLID principles

There are five principles to follow to ensure our application meets the SOLID requirements. These are as below,

  1. Single Responsibility Principle (SRP)
  2. Open Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

The Dependency Inversion Principle (DIP)

The Dependency Inversion Principle (DIP) states that a high-level class must not depend upon a lower-level class. They must both depend upon abstractions. And secondly, an abstraction must not depend upon details, but the details must depend upon abstractions. This will ensure the class and, ultimately, the whole application is very robust and easy to maintain and expand, if required. Let us look at this with an example.

Let us create a new .NET Core 3.1 console application in Visual Studio 2019 Community Edition as below.

SOLID Principles In C# - Dependency Inversion Principle

SOLID Principles In C# - Dependency Inversion Principle

Inside this project, I have created a new class file “DependencyInversionPrinciple”. In this file, I created the following classes.

// Not following the Dependency Inversion Principle
public class SalaryCalculator
{
    public float CalculateSalary(int hoursWorked, float hourlyRate) => hoursWorked * hourlyRate;
}
public class EmployeeDetails
{
    public int HoursWorked { get; set; }
    public int HourlyRate { get; set; }
    public float GetSalary()
    {
        var salaryCalculator = new SalaryCalculator();
        return salaryCalculator.CalculateSalary(HoursWorked, HourlyRate);
    }
}

These classes do not follow the “Dependency Inversion Principle” as the higher-level class EmployeeDetails is directly dependent upon the lower-level SalaryCalculator class.

We can fix this issue as below.

// Following the Dependency Inversion Principle
public interface ISalaryCalculator
{
    float CalculateSalary(int hoursWorked, float hourlyRate);
}
public class SalaryCalculatorModified : ISalaryCalculator
{
    public float CalculateSalary(int hoursWorked, float hourlyRate) => hoursWorked * hourlyRate;
}
public class EmployeeDetailsModified
{
    private readonly ISalaryCalculator _salaryCalculator;
    
    public int HoursWorked { get; set; }
    public int HourlyRate { get; set; }
    public EmployeeDetailsModified(ISalaryCalculator salaryCalculator)
    {
        _salaryCalculator = salaryCalculator;
    }
    public float GetSalary()
    {
        return _salaryCalculator.CalculateSalary(HoursWorked, HourlyRate);
    }
}

In the above code, we see that we have created an interface ISalaryCalculator, and then we have a class called SalaryCalculatorModified that implements this interface. Finally, in the higher-level class EmployeeDetailsModified, we only depend upon the ISalaryCalculator interface and not the concrete class. Hence, when we create the EmployeeDetailsModified class, we specify the abstraction implementation to use. In addition to this, the details of the CalculateSalary function are hidden from the EmployeeDetailsModified class, and any changes to this function will not affect the interface being used. Hence, we can see that in this new design, the higher-level class does not depend upon the lower-level class but on an abstraction, and the abstraction does not depend upon the details. To create the EmployeeDetailsModified class, we use the below code.

var employeeDetailsModified = new EmployeeDetailsModified(new SalaryCalculatorModified());
employeeDetailsModified.HourlyRate = 50;
employeeDetailsModified.HoursWorked = 150;
Console.WriteLine($"The Total Pay is {employeeDetailsModified.GetSalary()}");

Summary

In this article, we have looked at implementing the Dependency Inversion Principle (DIP) in a practical example. I would recommend you look though your existing classes and identify places where you have violated this principle and then think of ways to fix it. This will help to get you thinking in terms of applying this principle and help you to apply it to your code in the future as well. This article completes the five SOLID principles.


Similar Articles