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.
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.