Rob Sutherland’s Musings on Life, Code, and Anything Else

DIP: Dependency Inversion Principle

This is the D of the SOLID series.

The Dependency Inversion Principle is a way to decouple pieces of application code. The principle states:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
  2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

While this may sound overly complex, it isn't. It is also important to note that if we properly implement LSP and ISP then DIP is gained almost by default.

Perhaps a more practical example: The low level interfaces—database, email management, IO, etc.—should be designed with abstractions in mind. The high level modules—controllers, etc.—should depend on these abstractions and not specific implementation details. Low level object should never depend on higher level objects.

DIP allows objects to be loosely instead of tightly coupled. Loose coupling allows for elements in the software to change more easily. A simple way to think of this is that if a high-level module is having to "new up" an object, that may be a place where the dependency can be inverted.

public class SomeController : Controller
{
     private readonly IWidgetBuilder _widgetBuilder;

     public SomeController() {
           _widgetBuilder = new SimpleWidgetBuilder();
     }
}

DIP is often used with some form of Dependency Injection. We used Sanity for this in the past and with .NET Core, Dependency Injection is built into the framework. This allows the application, at startup, to set up all the dependencies that it needs to run. The objects that need those dependencies then have them "injected" at the proper point by the tooling. With Sanity, we had to setup the dependency and manually inject the dependency. With .NET Core, we no longer have to manually inject the dependency.

If we change our previous code to follow DIP we have to remove the new from the constructor code.

public class SomeController : Controller
{
     private readonly IWidgetBuilder _widgetBuilder;

     public SomeController(IWidgetBuilder widgetBuilder) {
           _widgetBuilder = widgetBuilder;
     }
}

That's a simple change, but by inverting the dependency we no longer tie our controller code to a specific implementation of IWidgetBuilder. We have loosely coupled the controller with the IWidgetBuilder interface and it no longer cares which implementation it is using. The controller depends only on the abstraction.

Some Points to Remember

  1. Stack the layers: We want to have as small of interfaces as possible at the lowest level of code. As we move up the code base to the higher level modules the interfaces may get larger. Those higher level modules may depend on several lower level modules. They may be composed of two ore more lower level abstractions.
  2. Naming becomes very important: Intent must be clear to avoid confusion within the system.
  3. If you're writing code in a high level module and passing around other low-level interfaces to other low-level interfaces that's probably an indication that DIP isn't being followed.
  4. A higher-level module should have a reasonable limit of how many lower-level modules it depends upon. Think of a constructor that requires 1 parameter versus a constructor that requires 8. Which one is more likely to be easier to test? This goes back to "Stack the layers" above. Compose mid-level interfaces of lower-level interfaces then higher-level objects can depend on those mid-level interfaces.