Saturday, October 14, 2017

SOLID - 5. The Dependency Inversion Principle

All good things must come to an end, and so must this blog post series on the five SOLID design principles. But, before we dive into the topic and discuss the last remaining principle, remember that the five are a team that belongs together.

You will find it difficult to violate only one of the five without violating another one aswell. It just might not be obvious to you at the time.



You can read up on my other articles on SOLID here:


The Dependency Inversion Principle


The Dependency Inversion Principle says that:

  • High level modules should not rely on low level modules. Both should depend on abstractions
  • Abstractions should not depend on details. Details must depend on abstractions.

Reversing dependency causes a client to not be fragile to changes related to implementation details. That is, changing the detail does not break the client. In addition, the same client can be reused with another implementation detail.

Let's make this clearer with examples. 

Look at the classic example of the button and the lamp, where both the Button and Lamp classes are concrete classes:

public class Button
{
    private Lamp _lamp;
    public void TurnOn()
    {
        if (condition)
            _lamp.Light();
    }
}

The above design violates the DIP since Button depends on a concrete class Lamp. That is, Button knows implementation details instead of identifying an abstraction for the design.
What abstraction would that be? Button must be able to handle some action and turn on or off some device, whatever it is: a lamp, an engine, an alarm, etc.

Inverting Dependence

The solution below reverses the button dependency for the lamp, making both now depend on the abstraction Device:

public class Button
{
    private IDevice _device;
    public void TurnOn()
    {
        if (condition)
            _device.TurnOn();
    }
}

public interface IDevice
{
    void TurnOn();
    void TurnOff();
}

public class Lamp : IDevice
{
    public void TurnOn()
    {
    }
    public void TurnOff()
    {
    }
}

Dependency Inversion Principle and software architecture

The Dependency Inversion Principle is one of the pillars for good software architecture, problem-solving focused, and flexible implementation details such as databases, web services, file read / write, and so on.

This principle reinforces that abstraction is more related to its client than to the server (the abstraction class). In the example shown above, Device (the abstraction) is directly connected to the client (Button). Its implementation (Lamp) is a mere detail.
Thus, Device would be in the same package (or component) of the Button and not together with its Lamp implementation.

Another very common example of this pattern is the use of the Repository pattern. In this case, we apply the Dependency Inversion Principle so that our domain depends on an abstraction of the Repository, being totally isolated of details about persistence.

Taking the interface to the client, we are saying "the client works this way" and who implements the interface (in another component) must meet this requirement. That is, the interface will only change by necessity of the client.

Finally, when there are multiple clients in separate components for the same interface, it stays in a separate component. Going back to Button's example, when you put Device in a component itself (neither with the client nor with the server), it could be used by Button and any other object capable of turning on / off a device.

Conclusion

The Principle of Dependency Inversion is an essential principle for good object-oriented design, while the opposite leads to a cast and procedural design. Identifying abstractions and reversing dependencies ensures that the software is more flexible and robust, being better prepared for change.

So with this we close the series on the SOLID principles, which together form a set of good practices that we should have in our utility belt and that we should apply whenever we can to improve the quality of software design and architecture.