At last, our journey has come to an end.
But now it's time to face the final boss.
DIP aka Dependency Inversion Principle
The final principle of the SOLID bosses.
Gear up, drink your mana potion, and prepare for combat.
Table of Contents
What is the dependency inversion principle?
Let's google that.
We get this:
The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions.
I get it, it sounds confusing.
But let's break this down:
- Client: Your main class/code that runs the high-level module.
- High-Level Modules: Interface/Abstraction that your client uses.
- Low-Level Modules: Details of your interfaces/abstraction.
What it basically says that imagine you have a car and your different components are:
- Client: You as the person driving the car.
- High-Level Modules: The steering wheel and the gas/brake peddles.
- Low-Level Modules: Engine
Abstractions don't depend on details.
For me, it doesn't matter whether my engine has changed or not, I still should be able to drive my car the same way.
Details should depend upon abstractions.
I would not want an engine that causes the brake to double the speed.
Simple Example
Imagine you have a budget reporting system that reads from a database.
It will look something like this:
<?php
class BudgetReport {
public $database;
public function __construct($database)
{
$this->database = $database;
}
public function open(){
$this->database->get();
}
public function save(){
$this->database->insert();
}
}
class MySQLDatabase {
// fields
public function get(){
// get by id
}
public function insert(){
// inserts into db
}
public function update(){
// update some values in db
}
public function delete(){
// delete some records in db
}
}
// Client
$database = new MySQLDatabase();
$report = new BudgetReport($database);
$report->open();
Everything works fine, but this code violates the dependency inversion principle because our high-level module BudgetReport
concretely depends on the low-level module MySQLDatabase
.
This also violates the open-closed principle because what if we wanted a different kind of database such as MongoDB?
We will have to change the BudgetReport
class to have if-else statements for it not to break.
To fix this is pretty simple, instead of concretely relying on the database class, we should use an abstraction. We will create a DatabaseInterface
which will implement any kind of database we need and finally we can inject our database through the constructor.
interface DatabaseInterface {
public function get();
public function insert();
public function update();
public function delete();
}
class MySQLDatabase implements DatabaseInterface {
// fields
public function get(){
// get by id
}
public function insert(){
// inserts into db
}
public function update(){
// update some values in db
}
public function delete(){
// delete some records in db
}
}
class MongoDB implements DatabaseInterface {
// fields
public function get(){
// get by id
}
public function insert(){
// inserts into db
}
public function update(){
// update some values in db
}
public function delete(){
// delete some records in db
}
}
class BudgetReport {
public $database;
public function __construct(DatabaseInterface $database)
{
$this->database = $database;
}
public function open(){
$this->database->get();
}
public function save(){
$this->database->insert();
}
}
// Client
$mysql = new MySQLDatabase();
$report_mysql = new BudgetReport($mysql);
$report_mysql->open();
$mongo = new MongoDB();
$report_mongo = new BudgetReport($mongo);
$report_mongo->open();
Now our BudgetReport
does not depend concretely on the database class but on its abstraction DatabaseInterface
. This approach also follows the open-closed principle because to add any new database we don't have to change the BudgetReport
class. We just need to add a new database class that implements the DatabaseInterface
.
Why should I follow this principle?
Because code that violates this principle may become too coupled together, and that makes the code hard to maintain, unreadable, and prone to side effects.
Conclusion
In summary:
- The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions.
- Code that doesn't follow this principle can be too coupled, which means you will have a hard time managing the project.
If you have any questions, leave them down in the comments section.
Top comments (1)
Your example is a much easier way of "grokking" the principle than reading the definition, even it's name. I guess we need a name in order to "put a name" to a concept but sometimes solving a coding problem will lead to a solution where you use a principle without knowing it.
e.g. not sure what db I'll end up using for this project - I'll make it so I can switch databases easily. Which means creating a common interface for the crud methods, abstracting the db specific details away and passing the database in.