Découvrez le principe d’inversion de dépendance
Le cinquième et dernier chapitre du principe SOLID est le principe d’inversion de dépendances : "Dependency Inversion Principle".
Je vous partage l’interprétation complète de ce principe : les classes de haut niveau ne devraient pas dépendre directement des classes de bas niveau, mais d’abstractions.
Vous devez vous demander : "Est-ce normal que je ne comprenne pas, à ce stade ?" :(
Laissez-moi le temps de détailler ce principe. :ange: Tout d’abord, un peu de vocabulaire !
Quand on conçoit un logiciel en programmation orientée objet, on peut faire la distinction entre deux types de classes :
Les classes de bas niveau qui implémentent des fonctionnalités de base : écrire dans un fichier, se connecter à une base de données, retourner une réponse HTTP...
Les classes de haut niveau qui concernent le métier de vos applications (ce que l’on appelle communément la "business logic") : la gestion des coûts de livraison, par exemple.
Puisque vos classes de haut niveau auront besoin de classes bas niveau, vous aurez commencé par écrire les classes de bas niveau en premier. Le problème de cette approche est que vos classes de haut niveau ont tendance à dépendre directement des classes de bas niveau.
Par exemple, prenons l’exemple d’une classe UserRepository
dont la responsabilité est de récupérer une liste d’utilisateurs. Pour cela, nous avons développé une classe Database
capable de faire des requêtes à la base de données.
<?php
use Database;
use User;
class UserRepository
{
private $database;
public function __contruct(Database $database)
{
$this->database = $database;
}
public function getAll()
{
$users = [];
$results = $this->database
->execute('SELECT * FROM users')
->fetchAll()
;
foreach($results as $result) {
$users[] = new User($result);
}
return $users;
}
}
Dans cet exemple, la classe de haut niveau ( UserRepository
) dépend directement d’une classe de bas niveau ( Database
). C’est problématique, car si un jour nous voulons utiliser autre chose qu’une base de données pour retrouver les utilisateurs (disons une API REST), nous serons obligés de changer le code de cette classe (ce qui est incompatible avec le principe "Open Closed Principle").
La solution consiste à inverser le sens de la dépendance, à faire en sorte que ce soient les classes de bas niveau qui dépendent d’abstractions !
Et des abstractions, vous en connaissez au moins deux : les classes abstraites et les interfaces. :magicien:
Mettez en application le principe d'inversion de dépendance
Mettons à jour le code précédent en commençant par définir une nouvelle interface indiquant qu'une classe de type DatabaseInterface
doit au moins permettre d'exécuter une requête et de récupérer des données :
<?php
interface DatabaseInterface
{
public function execute($query) : self;
public function fetchAll() : array;
}
Maintenant, notre classe bas niveau peut implémenter l’interface (nous allons la renommer au passage) :
<?php
use DatabaseInterface;
class MySQL implements DatabaseInterface
{
public function execute($query)
{
/*...*/
}
public function fetchAll()
{
/*...*/
}
}
Et voici le code de la classe d’exemple mis à jour !
<?php
use DatabaseInterface;
use User;
class UserRepository
{
private $database;
public function __contruct(DatabaseInterface $database)
{
$this->database = $database;
}
public function getAll()
{
$users = [];
$results = $this->database
->execute('SELECT * FROM users')
->fetchAll()
;
foreach($results as $result) {
$users[] = new User($result);
}
return $users;
}
}
$userMySQLRepository = new UserRepository(new MySQL());
$userRestApiRepository = new UserRepository(new RestApiClient());
Mais qu’est-ce que cela change, finalement ?
Eh bien, avant, c’était la classe de haut niveau qui devait respecter l’implémentation des fonctions de bas niveau. Et maintenant, ce sont les classes de bas niveau qui vont devoir respecter les contraintes des classes de haut niveau. Comme vous le voyez dans l’exemple mis à jour, cela nous permet d’imaginer le support de plusieurs systèmes de persistance, comme l’appel à une API REST.
Si l’on devait résumer ce que nous venez d’apprendre de ce principe, vous devriez toujours dépendre d’abstractions (les interfaces) et pas d’implémentations concrètes (les classes).
Exercez-vous !
Pour conclure ce chapitre de pratique, pas un projet complet cette fois mais plutôt un exercice à "trous" !
Sur le modèle de la mise en application décrite précédemment, vous allez compléter un programme de récupération d'utilisateurs dans différents systèmes de données :
des utilisateurs stockés dans des tableaux ;
des utilisateurs stockés sous forme de fichiers JSON.
Un script de test appelé app.php
est disponible dans l'archive de l'exercice à télécharger, et le code à compléter est identifié par des commentaires.
L'objectif pour vous est de bien comprendre comment les dépendances entre les classes sont organisées, et dans quel sens vous devez les organiser pour obtenir un code SOLIDe !
En résumé
Les principes SOLID vous permettent de rendre votre code facile à maintenir et à faire évoluer, le tout en le gardant compréhensible. En reprenant du code de mauvaise qualité, vous êtes parvenu à en améliorer la qualité en quelques heures. Ce que vous venez d’apprendre est donc utilisable sur n’importe quel projet, pour peu que vous décidiez d’y investir du temps !
S pour une Seule responsabilité par classe, par fichier ;
O pour Ouvert à l’extension, mais Fermé aux modifications ;
L pour la très célèbre Barbara Liskov, mais surtout pour retenir qu’une classe enfant doit respecter les mêmes contrats que sa classe parente ;
I pour une seule responsabilité par Interface, si possible, et cohérente avec votre projet ;
D pour l’inversion de Dépendances : ce sont les classes de bas niveau qui doivent dépendre des contrats de haut niveau, et pas le contraire !
Maintenant que nous avons vu comment rendre notre code plus robuste avec les principes SOLID, je vous invite à découvrir comment identifier les mauvaises pratiques de conception avec STUPID. Suivez-moi au prochain chapitre !