SOLID Design Principles in C# - Part 5 - Dependency Inversion

2022, Sep 22

The Dependency Inversion Principle (DIP) is a principle that helps us to create more resilient classes, minimizing any impact of changes. At the same time, it promotes reusability.

This post is part of a series where we explore the SOLID design principles, arguably the most popular design principles for object-oriented software development.

Dependency Inversion Principle

Depend on abstractions, not on concretions. Robert C. Martin

The principle also mentions that:

  • High-level modules should not depend upon low-level modules, both should depend upon abstractions.
  • Abstractions should not depend upon details. Details should depend upon abstractions.

We can associate this principle with decoupling because the idea is to disassociate module dependencies. This has great benefits:

  • Resiliency - things are not going to easily break, minimizing the impact of changes.
  • Reusability - because of the dependency on abstractions, the code becomes much more flexible and reusable.
  • Testability - decoupling makes it easier to unit test.

Abstraction can be compared to a process of hiding the implementation details from the client but making the functionality available to this client. In other words, the client has the information on what the object does instead of how it does it.

Interfaces - Since all the methods of an interface are abstract and the client doesn't know how a method is written (except the method signature/prototype), abstraction is achieved when using interfaces. Interfaces also promote decoupling, as we have already seen it in (OCP)1 and (ISP)2.

The wrong example

The same example as (SRP)3, which uses concretions in constructors:

public class OrderProcessor
{
    private readonly OrderValidator _orderValidator;
    private readonly OrderSaver _orderSaver;
    private readonly OrderNotifier _orderNotifier;

    public OrderProcessor(OrderValidator orderValidator, OrderSaver orderSaver, OrderNotifier orderNotifier) // should we use concretions?
    {
        _orderValidator = orderValidator;
        _orderSaver = orderSaver;
        _orderNotifier = orderNotifier;
    }
    
    public void Process()
    {
        _orderValidator.Validate();
        _orderSaver.Save();
        _orderNotifier.Notify();
    }
}

The right example

Depend on abstractions, not on concretions.

public class OrderProcessor
{
    private readonly IOrderValidator _orderValidator;
    private readonly IOrderSaver _orderSaver;
    private readonly IOrderNotifier _orderNotifier;

    public OrderProcessor(IOrderValidator orderValidator, IOrderSaver orderSaver, IOrderNotifier orderNotifier)
    {
        _orderValidator = orderValidator;
        _orderSaver = orderSaver;
        _orderNotifier = orderNotifier;
    }
    
    public void Process()
    {
        _orderValidator.Validate();
        _orderSaver.Save();
        _orderNotifier.Notify();
    }
}

By using the interfaces, the high-level OrderProcessor is not depending on low-level modules, but on abstractions represented by the contracts IOrderValidator, IOrderSaver and IOrderNotifier.

As DIP is the last SOLID principle, we have gotten to the end of this series. I invite you to review the full series below:

Full Series

Single-Responsibility Principle (SRP)

Open-Closed Principle (OCP)

Liskov Substitution Principle (LSP)

Interface Segregation Principle (ISP)

Dependency Inversion Principle (DIP)


  1. OCP OCP
  2. ISP ISP
  3. SRP SRP