CodeOath
← All posts
Architecture & Patterns80 min total · 26 parts

ACID, SOLID, and Common Design Patterns: A Software Design Reference

Contents — Part 11 of 26: Dependency Inversion Principle
Part 11 of 26 · ~2 min

Dependency Inversion Principle

High-level modules shouldn't depend on low-level modules directly — both should depend on abstractions. "High-level" means the code expressing business policy (an order-processing workflow); "low-level" means the code doing concrete technical work (talking to a specific SQL database, a specific email provider). Without inversion, a change to the low-level detail forces a change to the high-level policy that has nothing to do with the change being made.

// Violates DIP — OrderService is directly coupled to SqlOrderRepository.
// Switching databases, or unit-testing OrderService without a real database,
// both require modifying OrderService itself.
public class SqlOrderRepository
{
    public void Save(Order order) { /* talks directly to SQL Server */ }
}

public class OrderService
{
    private readonly SqlOrderRepository _repository = new SqlOrderRepository();
    public void PlaceOrder(Order order) => _repository.Save(order);
}
// Both OrderService and SqlOrderRepository now depend on the IOrderRepository
// abstraction, rather than OrderService depending on SqlOrderRepository directly.
public interface IOrderRepository
{
    void Save(Order order);
}

public class SqlOrderRepository : IOrderRepository
{
    public void Save(Order order) { /* talks directly to SQL Server */ }
}

public class InMemoryOrderRepository : IOrderRepository
{
    private readonly List<Order> _orders = new();
    public void Save(Order order) => _orders.Add(order); // trivial to use in a unit test
}

public class OrderService
{
    private readonly IOrderRepository _repository;
    public OrderService(IOrderRepository repository) => _repository = repository; // supplied, not constructed
    public void PlaceOrder(Order order) => _repository.Save(order);
}

It's worth being precise about a distinction that gets blurred constantly: Dependency Inversion is the principle (depend on abstractions, not concretions); Dependency Injection is a technique for satisfying it (supplying a class's dependencies from the outside — via constructor, in the example above — rather than having the class construct them itself). A DI container (built into ASP.NET Core, for example) is just a tool that automates wiring up which concrete class satisfies which interface at startup; it isn't what makes the principle true. You can follow Dependency Inversion by hand, with no container at all, by passing interfaces into constructors exactly as shown above.