This article is an overview of DIP, for an in-depth look at the Dependency Inversion Principle, you can read uncle Bob's article on DIP.

Defining Bad Design

If a software product is badly designed: it is rigid, fragile and immobile. So let's define those 3 properties, we can say that any software product:

  • Is rigid if any change has multiple consequences.
  • Is fragile if a single change results in unexpected bugs.
  • Is immobile if we cannot easily extract parts of our system for reuse in other software products.

An Example Of Bad Design

Let's consider an example of bad design: we are going to model a system of a Button and a Lamp. Our Button class can be switched on or off, when our Button is switched on, the Lamp is switched on and when the Button is switched off, the Lamp is switched off. Our Button class uses the Lamp class.

Our code in the above example violates the Dependency Inversion Principle. As stated above, the Dependency Inversion Principle solves bad design. But we haven't defined the Dependency Inversion Principle, which can be stated as:

High level classes should not depend upon low level classes, both should depend on abstractions.
Abstractions should not depend on details, details should depend on abstractions.

At this point, we would want to know which classes are high level, and which are low level? In general, the class that uses another class is the high level class. So our Button class is the high level class and the Lamp class is the low level class.

How does our code in it's current iteration, not comply with the Dependency Inversion Principle:

  • The Button class is dependant on the Lamp class.
  • We cannot re-use the Button class without also including the Lamp class.
  • Any update, like creating a new class which needs to be turned on or off by our Button cannot be added without modifying the Button class itself.

How Do We Conform To The DIP?

For our code to conform with the Dependency Inversion Principle, we must invert the dependancies of our code:

  • Button must not depend on Lamp, both must depend on abstractions (interfaces).
  • We must be able to easily re-use Button and Lamp, Button must not reference Lamp directly.

Our updated Button and Lamp now both depend on abstractions, ButtonInterface and DeviceInterface respectively.

In General, How Do We Implement the Dependency Inversion Principle?

When we have class A which is used by class B:

We could abstract the methods used by class B into an interface that is implemented by class A.

Applying the same principle to class B, i.e making class B depend on an abstraction, so that any future version of our code which uses class B will also depend on an abstraction, our code becomes:

Key Takeaways

  • The Dependency Inversion Principle aims to remedy bad design resulting from rigidity, fragility and immobility.
  • Classes that use other classes should both depend on abstractions and not directly on each other.
  • The Dependency Inversion Principle in PHP can be implemented using interfaces.