What is the Dependency Inversion Principle?
Robert Martin defines it as “Depend on abstractions, not on concretions or, A. High-level modules should not depend upon low-level modules. Both should depend upon abstractions. Abstractions should not depend upon details B. Abstraction should not depend upon details. Details should depend upon abstractions.”
If you use both OCP and LSP strictly, you will notice how a new pattern or structure emerges from it that can be generalized into what is known as the Dependency Inversion Principle.
This is one of the most useful principles, as it allows us to design software that is flexible (easy to change or extend), robust (reacts well to changes i.e. doesn’t break everywhere) and reusable (the parts of the system are very decoupled and we can extract them and use them in other projects), and whose main aim is to address bad design. The cause of badly designed software – software that is rigid, fragile, and immobile (opposite to flexible, robust and reusable in this case) – is the heavy hard-coded dependencies between its modules. These dependencies can, in turn, force the need of a cascade of changes when we want to introduce a tiny little change in the system (rigidity), or can result in a chain of unexpected errors (fragility), and of course, make impossible to reuse code in other applications because everything is so entwined that we might as well bring together the whole system.
The DIP addresses this problem saying no to hard-coded and top-down dependencies. The high- level modules should not depend upon the low-level modules, everything has to depend upon abstractions (thereby we get and “inverted” dependency). This way, the high-level modules don’t know exactly what they depend upon, they just know they are using something that must adhere to a given interface, and thus, everything that follows that interface contract can be plugged in (or plugged out). If you extend the principle to the whole system you end up with a set of highly decoupled modules, that are completely isolated from changes in other modules and that can be easily reused. You end up with a well-defined set of interfaces or abstractions that define the policy of your system and a set of concrete implementations that are connected via these abstractions.