Dependency Inversion Principle

1 min read
On trails:CSSOLIDstep 6/6

High-level modules (for example business logic) should not depend directly on low-level modules (for example data access or specific external services). Both should depend on abstractions, such as interfaces. Furthermore, abstractions should not depend on details. Details should depend on abstractions.

In C#

A NotificationService (high-level) that directly creates instances of EmailSender and SMSSender (low-level) violates DIP. To follow DIP, NotificationService depends on an IMessageSender interface. EmailSender and SMSSender implement IMessageSender. The specific sender instance is then injected into NotificationService, for example through constructor injection using a DI container (see dependency injection in ASP.NET CoreDependency injection and service lifetimesHow ASP.NET Core hands each class what it needs, and how long each instance lives.Opens in a new tab).

First, the violation. The service creates its own EmailSender.

C#
// Violation of DIPpublic class EmailSender_Bad { public void Send(string message) { /* sends email */ } }public class NotificationService_Bad{    private EmailSender_Bad _emailSender = new EmailSender_Bad(); // Direct dependency    public void NotifyUser(string message) { _emailSender.Send(message); }}

Then the version that follows DIP. The IMessageSender abstraction comes first, and the dependency is injected through the constructor.

C#
// Adherence to DIPpublic interface IMessageSender { void SendMessage(string message); }public class EmailSender_Good : IMessageSender { public void SendMessage(string message) { /* sends email */ } }public class SMSSender_Good : IMessageSender { public void SendMessage(string message) { /* sends SMS */ } }public class NotificationService_Good{    private readonly IMessageSender _messageSender;    public NotificationService_Good(IMessageSender messageSender) // Dependency is injected    {        _messageSender = messageSender;    }    public void NotifyUser(string message) { _messageSender.SendMessage(message); }}

Where it matters

DIP is fundamental for creating loosely coupled, flexible systems. It lets different implementations of low-level modules be swapped without affecting high-level modules. That greatly improves testability (by allowing dependencies to be mocked) and maintainability. DIP is a cornerstone of modern modular design, particularly in microservices, or in any complex system where components need to evolve independently.


Sources