One of the nicest things you can add to your programming skills is coding to interface. One of the five principles of S.O.L.I.D is Dependency inversion principle which states:
In object-oriented programming, the dependency inversion principle refers to a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details. The principle states:1
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend on details. Details should depend on abstractions.
Pretty formal definition hun ? let's understand through example actually.
Imagine we have rudimentary MySQL wrapper class:
class Mysql
{
protected $db = null;
public function connect($dsn, $user = '', $pass = '')
{
$this->db = new PDO($dsn, $user, $pass);
}
public function query($query)
{
return $this->db->query($query);
}
}
Pretty common and valid code in old days we used to be proud while writing such class. There are couple of problems associated with it though:
- The class
Mysql
has implicit dependency (tight-coupling) on thePDO
class. All dependencies should always be explicit not implicit. - It is hard to unit-test due to implicit dependency
Let's use above class:
class User
{
private $database = null;
public function __construct()
{
$this->database = new Mysql();
$this->database->connect('mysql:host=localhost;dbname=test', 'root', '');
}
public function getUsers()
{
$users = $this->database->query('SELECT * FROM users ORDER BY id DESC');
print_r($users);
}
}
$user = new User();
$user->getUsers();
Again pretty common piece of code but again this one has even more problems:
- The class
User
has implicit dependency (tight-coupling) on theMysql
class. All dependencies should always be explicit not implicit. - It is hard to unit-test due to implicit dependency
- If we wanted to change database credentials, we need to edit the
User
class which is not good; every class should be completely modular or black box. If we need to operate further on it, we should actually use its public properties and methods instead of editing it again and again. - Let's assume right now class is using MySQL as database. What if we wanted to use some other type of database ? You will have to modify class.
- The
User
class does not necessarily need to know about database connection, it should be confined to its own functionality only. So writing database connection code inUser
class doesn't make it modular. This defeats the Single responsibility principle.
You might wonder above code has problems but these days we write code based on MVC design pattern and follow best practices and write modern code but I am sure below code is pretty common even today using any framework you use:
class UserController extends Controller
{
public function actionListAllUsers()
{
// fetch all users from database
$users = User::model()->findAll();
// show them on screen
print_r($users);
}
}
If you analyze this has same problems again in that class UserController
is tightly coupled (implicit dependency) with User
model class which also makes it hard to unit-test.
This means:
Coupled Code is Evil BIG DOT
So what's the solution ?
The solution is Dependency Injection (see my previous post Dependency Injection in PHP). The idea behind Dependency Injection is that instead of writing implicit dependencies, we should pass/provide those via constructor or setter method or even interface.
Let's refactor User
class code and pass our dependency via constructor instead of hard-coding it inside User
class method:
class User
{
private $database = null;
public function __construct(Mysql $database)
{
$this->database = $database;
}
public function getUsers()
{
$users = $this->database->query('SELECT * FROM users ORDER BY id DESC');
print_r($users);
}
}
$database = new Mysql();
$database->connect('mysql:host=localhost;dbname=test', 'root', '');
$user = new User($database);
$user->getUsers();
This is much better! We no more have implicit dependency on Mysql
class which also makes our User
class easier to unit-test. The class makes clear intention that it needs some sort of Mysql
class. It also doesn't know (or care) where Mysql
will come form, it just needs one to operate. The User
class is now completely modular and follows single responsibility principle (that of manipulating users).
In the similar way, we can also improve our modern code:
class UserController extends Controller
{
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function actionListAllUsers()
{
// fetch all users from database
$users = $this->user->findAll();
// show them on screen
print_r($users);
}
}
Here again we pass dependency (User
class) of UserController
via its constructor making it much better. So all good ? Not really...
We still have a problem
One problem with User
class which still persists is that it is tied to specific type of database (Mysql
). What if we wanted to use some other database like Sqlite or SQL Server or some other ? Well this is where dependency inversion principle comes into play.
To overcome this problem, we need to create an interface (abstraction or contract - see definition of DIP above) which would allow us to use any database with User
class. Here is our interface that all databases would implement:
interface DB
{
public function connect($dsn, $user = '', $pass = '');
public function query($query);
}
Here is Mysql
class now implementing above interface:
class Mysql implements DB
{
protected $db = null;
public function connect($dsn, $user = '', $pass = '')
{
$this->db = new PDO($dsn, $user, $pass);
}
public function query($query)
{
return $this->db->query($query);
}
}
Another database class implementing above interface:
class Sqlite implements DB
{
protected $db = null;
public function connect($dsn, $user = '', $pass = '')
{
$this->db = new PDO($dsn);
}
public function query($query)
{
return $this->db->query($query);
}
}
As you can see both Mysql
and Sqlite
implement methods imposed by the interface. Finally here is our User
class:
class User
{
private $database = null;
public function __construct(DB $database)
{
$this->database = $database;
}
public function getUsers()
{
$users = $this->database->query('SELECT * FROM users ORDER BY id DESC');
print_r($users);
}
}
Notice that in constructor of above class, we are now passing interface rather than specific type of database.
Now we can use any database for the User
class, here is example of Mysql
:
$database = new Mysql();
$database->connect('mysql:host=localhost;dbname=test', 'root', '');
$user = new User($database);
$user->getUsers();
And example of Sqlite
database class:
$database = new Sqlite();
$database->connect('sqlite:database.sqlite');
$user = new User($database);
$user->getUsers();
Amazing! we can now readily change database to be used with our User
class, our User
class is much more flexible by following the dependency inversion principle. If client requires to use some other database, no problem we implement one and specify to User
class in similar fashion!
As you can see we took basic and common example of code and converted it into re-usable, flexible, modular and testable piece of code.
You might want to read more about S.O.L.I.D principles to write pleasant code and as bonus avoid S.T.U.P.I.D code.
Top comments (0)