Dependency Inversion Principle: Decouple to Gain Control

How to make high-level code independent from low-level details

Posted by Hüseyin Sekmenoğlu on October 09, 2018 Object-Oriented Programming (OOP)

🔁 What is the Dependency Inversion Principle?

The Dependency Inversion Principle (DIP) is the final principle in the SOLID acronym and plays a key role in building flexible and maintainable software. It encourages developers to reverse traditional dependency direction. Instead of high-level modules depending on low-level implementations, both should depend on abstractions.

It also states that abstractions should not depend on details. Instead, details should depend on abstractions.


📄 A Problematic Example

Suppose you have a Document class with methods to convert documents into different formats like PDF or Excel:

public class Document
{
    public void ConvertToPdf()
    {
        var converter = new PdfConverter();
        converter.Convert(this);
    }

    public void ConvertToExcel()
    {
        var converter = new ExcelConverter();
        converter.Convert(this);
    }
}

This seems straightforward but it causes tight coupling. The Document class now directly depends on the concrete implementations of PdfConverter and ExcelConverter. If these classes change or if you want to replace the PDF conversion with a new library, you must change the Document class too.


🔧 Refactoring With DIP

To follow the Dependency Inversion Principle, extract an interface for the conversion process:

public interface IConverter
{
    void Convert(Document document);
}

Both converters will implement this interface:

public class PdfConverter : IConverter
{
    public void Convert(Document document)
    {
        // Convert to PDF
    }
}

public class ExcelConverter : IConverter
{
    public void Convert(Document document)
    {
        // Convert to Excel
    }
}

Now update the Document class to depend on the abstraction:

public class Document
{
    private readonly IConverter _converter;

    public Document(IConverter converter)
    {
        _converter = converter;
    }

    public void Convert()
    {
        _converter.Convert(this);
    }
}

By doing this, Document no longer cares about what kind of converter it is using. This design is more flexible and easier to test.


✅ Benefits of Dependency Inversion

  • Reduces coupling between high-level and low-level modules

  • Makes unit testing easier by allowing dependency mocking

  • Encourages the use of interfaces and abstractions

  • Enables changes to low-level implementations without affecting high-level logic


🧠 Key Takeaways

  • Both high-level and low-level modules should depend on abstractions

  • Details must depend on abstractions not the other way around

  • Constructor injection is a common method for applying DIP

  • DIP makes your code more flexible and easier to extend


📌 Summary

The Dependency Inversion Principle empowers you to write modular code that can grow and change without breaking existing functionality. By depending on abstractions instead of concrete classes, you achieve a cleaner separation of concerns that leads to better design and testability.