DEV Community

Cover image for SOLID: Dependency Inversion Principle (DIP) in C#
Aditya
Aditya

Posted on

SOLID: Dependency Inversion Principle (DIP) in C#

Introduction

The Dependency Inversion Principle (DIP) is one of the foundational principles of object-oriented programming and software design, forming the "D" in the SOLID acronym. This principle emphasizes designing software modules to reduce coupling and improve flexibility, scalability, and maintainability.

Code dependency

In this article, we'll delve into the essence of DIP and understand its importance through practical examples in C#.

What Is the Dependency Inversion Principle?

The Dependency Inversion Principle states:

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

Simply put, the principle encourages us to depend on interfaces or abstract classes rather than concrete implementations. This makes the system easier to modify and extend without impacting other parts of the code.

A Problem Without DIP ❌

Code bug

Consider an e-commerce application where we need to send notifications via email. Here's a naive implementation:

public class EmailNotification
{
    public void Send(string message)
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

public class OrderProcessor
{
    private readonly EmailNotification _emailNotification;

    public OrderProcessor()
    {
        _emailNotification = new EmailNotification();
    }

    public void ProcessOrder()
    {
        // Order processing logic
        Console.WriteLine("Order processed.");

        // Send notification
        _emailNotification.Send("Your order has been processed.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, OrderProcessor is tightly coupled to EmailNotification. If we want to add support for SMS notifications, we'll have to modify the OrderProcessor class, violating the Open/Closed Principle (OCP).

Applying DIP to the Solution

Correct

To follow DIP, we'll introduce an abstraction (INotification) that both EmailNotification and SmsNotification can implement. The OrderProcessor will depend on the abstraction, not the concrete implementations.

Step 1: Define the Abstraction

public interface INotification
{
    void Send(string message);
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Implement the Abstraction

public class EmailNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

public class SmsNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine($"SMS sent: {message}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Refactor OrderProcessor to Use the Abstraction

public class OrderProcessor
{
    private readonly INotification _notification;

    public OrderProcessor(INotification notification)
    {
        _notification = notification;
    }

    public void ProcessOrder()
    {
        // Order processing logic
        Console.WriteLine("Order processed.");

        // Send notification
        _notification.Send("Your order has been processed.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Inject the Dependency

Using dependency injection, we can decide the notification mechanism at runtime.

class Program
{
    static void Main(string[] args)
    {
        // Use EmailNotification
        INotification emailNotification = new EmailNotification();
        var orderProcessor = new OrderProcessor(emailNotification);
        orderProcessor.ProcessOrder();

        // Switch to SmsNotification
        INotification smsNotification = new SmsNotification();
        var smsOrderProcessor = new OrderProcessor(smsNotification);
        smsOrderProcessor.ProcessOrder();
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits of Applying DIP

  • Reduced Coupling: OrderProcessor is no longer dependent on specific implementations (EmailNotification or SmsNotification).
  • Extensibility: Adding new notification types, such as push notifications, requires no changes to OrderProcessor.
  • Testability: The use of abstractions makes it easy to mock INotification in unit tests

Conclusion

The Dependency Inversion Principle promotes flexibility and adaptability in software design by emphasizing the use of abstractions. By adhering to DIP, we create systems that are more maintainable, scalable, and resilient to change.

Thank-you

Let connect on LinkedIn and checkout my GitHub repos:

Top comments (0)