This is part four of a five part series about SOLID class design principles by Robert C. Martin. The SOLID principles focus on achieving code that is maintainable, robust, and reusable. In this post, I will discuss the Dependency Inversion Principle.

The Dependency Inversion Principle (DIP): High level modules should not depend upon low level modules. Both should depend upon abstractions.

Imagine a world without plugs and sockets. The computer is directly soldered into an electrical wire in the wall. Whenever you buy a motherboard you also get a mouse, keyboard and monitor, but they're all directly soldered onto the motherboard by the manufacturer. Everything works fine, but things get complicated when you want to remove or replace anything. If you try to replace the mouse:

  • You risk damaging the motherboard
  • It takes forever to solder
  • Soldering is error prone because the new mouse has different wires

A world without plugs and sockets sounds ridiculous. Yet we programmers have a tendency to pull out the metaphorical soldering iron and hard-wire our classes together as we're making them. If the classes are hard-wired together, and you want to replace one of them:

  • You risk damaging code that uses the class
  • It takes forever to find and change every place that the class is used
  • Inserting the replacement class is error prone because it is slightly different to the old class

The Dependency Inversion Principle is basically a way of adding plugs and sockets to your code. It allows you to create your high level modules (the computer) independent of the low level modules (the mouse). The low level modules can be created later, and are easily replaceable.

What Are The "Plugs"?

Depending on what programming language you're using, a "plug" can be a few different things. Here are plug mechanisms for a few common languages:

Language Plug
C++ base classes with virtual methods; templating; possibly preprocessor function macros
Java interfaces; base classes with virtual methods
Python duck typing
PHP interfaces; base classes with virtual methods; duck typing
Objective-C protocols; base classes

The plug is essentially an abstract interface. Any class can implement or inherit the abstract interface. Here is an example in C++:

//This is the "plug" (abstract base class)
class Exporter {
public:
    //pure virtual (not implemented)
    virtual String convertDocumentToString(Document* doc) = 0;
};

//This is a concrete class that implements the "plug"
class CSVExporter : public Exporter {
public:
    //concrete implementation
    String convertDocumentToString(Document* doc);
};


//Another concrete class that implements the "plug"
class XMLExporter : public Exporter {
public:
    //concrete implementation
    String convertDocumentToString(Document* doc);
};

It is critical that the all implementations/subclasses adhere to the Liskov Substitution Principle. This is because the implementations/subclasses will be used via the abstract interface, not the concrete class interface.

What Are The "Sockets"?

The "sockets" are any functions or classes that use a "plug". Continuing from the above code example, below is an example of a "socket" class. Error checking has been ignored for brevity.

//Class with an Exporter "socket"
class ExportController {
private:
    Exporter* m_exporter;
public:
    //this is the socket that accepts Exporter plugs
    void setExporter(Exporter* exporter);
    void runExport();
};

// ... (code omitted)

void ExportController::runExport()
{
    Document* currentDocument = GetCurrentDocument();
    String exportedString = m_exporter->convertDocumentToString(currentDocument);
    String exportFilePath = GetSaveFilePath();
    WriteStringToFile(exporterString, exportFilePath);
}

You may notice that the above example is practically the same as an example in a previous article about the Open Closed Principle (OCP). Both make use of dependency injection (DI), but for different reasons. The OCP uses DI to "close" a class to modification, whereas the DIP uses DI to remove the dependencies on lower level classes.

Bringing It All Together

In the above example, the ExportController is the "higher level" module and all the Exporter subclasses are the "lower level" modules. This is because ExportController uses the Exporter subclasses, and not the other way around. The application of the DIP means that ExportController has no knowledge of CSVExporter, XMLExporter, or any other Exporter subclass. It only knows about the abstract Exporter interface.

The ExportController class says "this is my socket. You lower level modules are responsible for comforming to it." The opposite of this is when the ExportController directly creates and uses XMLExporter and CSVExporter. In that situation, XMLExporter and CSVExporter say "this is my interface. Every other class is responsible for conforming to it." The higher level classes control the flow of the application, and therefore the user experience. You don't want some insignificant implementation detail dictating the flow of the application.