SOLID : Le guide simple avec des exemples applicables

  Temps de lecture : 12 min.
Comprendre les 5 principes SOLID et apprendre à les utiliser au quotidien en tant que développeur grâce à des exemples applicables.

SOLID, en informatique, c’est quelque chose dont on entend souvent parler.

Que ce soit entre développeurs ou via des formations, on retrouve souvent cet acronyme dans les slides de présentation.

D’ailleurs si tu en parles avec un autre dev, il te dira sûrement qu’il faut faire du code SOLID !

Que SOLID, ce sont les bonnes pratiques, que c’est génial, que c’est obligatoire pour progresser.

Est-ce vraiment le cas ?

Dans cet article, je te propose des exemples concrets en PHP de comment SOLID peut t’aider en tant que développeur.

Je vais te montrer que ce n’est pas que de la théorie et que tu peux utiliser SOLID tous les jours dans ton code pour le rendre plus qualitatif !

  1. Qu’est-ce que SOLID ?
  2. Pourquoi faire du code SOLID ?
  3. Comment utiliser SOLID dans son code (avec exemples concrets) ?
    1. Single Responsibility Principle
    2. Open/Closed Principle
    3. Liskov’s Substitution Principle
    4. Interface Segregation Principle
    5. Dependency Inversion Principle
  4. Sondage : Combien de développeurs utilisent SOLID au quotidien ?
  5. Conclusion

Connaître les principes SOLID c’est bien, mais les appliquer dans son code, c’est mieux.

Qu’est-ce que SOLID ?

Tu dois sans doute déjà connaître quelques initiales, les voici au complet.

SOLID, c’est un acronyme pour ces 5 principes de programmation.

  • S : Single Responsibility Principle
  • O : Open/Closed Principle
  • L : Liskov’s Substitution Principle
  • I : Interface Segregation Principle
  • D : Dependency Inversion Principle

Le but n’est pas de les connaître par cœur, mais de suffisamment les comprendre pour pouvoir les utiliser.

En informatique, ces principes sont considérés comme des bonnes pratiques pour les développeurs.

Nous sommes censés les appliquer afin de produire du code de qualité.

C’est ça SOLID.

À lire : Code de qualité : Comment bien coder ?

Pourquoi faire du code SOLID ?

SOLID est un ensemble de (seulement) 5 bonnes pratiques dont le but est de rendre le code :

  • Moins bogué
  • Plus facile à lire
  • Plus logique
  • Maintenable
  • Testable
  • Extensible (tu changes une partie du programme et il continue de fonctionner)

Comprendre SOLID et l’utiliser au quotidien te permettront surtout d’améliorer la qualité de ton code et de comprendre des codes plus évolués (comme celui de ton framework par exemple).

En une ligne : cela te fera devenir un meilleur développeur.

Tu as donc tout intérêt à comprendre ces principes, et mieux encore, à les appliquer dans ton quotidien de dev.

Faire du code SOLID diminuera grandement la dette technique de tes projets.

Une raison de plus de te former à son utilisation !

Comment utiliser SOLID dans son code (avec exemples concrets) ?

Dans cette partie, j’aimerais te montrer comment tu peux inclure du code SOLID dans ton projet sans que cela devienne compliqué.

Le plus gros frein à l’utilisation des principes de SOLID, c’est que tout le monde pense que c’est compliqué et que c’est réservé aux génies du dev.

Mais SOLID n’est pas si compliqué que ça à comprendre.

Le seul prérequis pour bien le maîtriser, c’est d’avoir un peu d’expérience en programmation.

⭐️ Je te conseille de mettre cet article en favoris car tu risques d’y revenir quelques fois afin de bien maîtriser tous les principes SOLID.

Avant de commencer, ne sois pas frustré de ne pas tout comprendre du premier coup.

Personnellement, j’ai mis des mois à assimiler ces notions.

Mais quand tu arriveras à comprendre et à utiliser les principes SOLID dans ton code, ta carrière de développeur fera un bond en avant.

* J’ai choisi le langage PHP pour illustrer les principes SOLID car c’est un langage que beaucoup de développeurs ont déjà utilisé.

S : Single Responsibility Principle (SRP)

C’est sans doute le principe le plus simple à comprendre dans SOLID.

Une classe ne doit avoir qu'une seule et unique responsabilité.

Une erreur que l’on retrouve beaucoup dans les projets, c’est d’avoir une classe type UserService.php avec tout et n’importe quoi dedans.

De plus, le nom UserService.php n’est pas du tout explicite, on ne sait pas ce que le fichier contient.

Double problème !

❌ Code qui ne respecte pas le principe de responsabilité unique (SRP)

Ici, UserService.php a plusieurs rôles (ou responsabilités).

  • Gérer la mise à jour d’un utilisateur
  • Gérer la session de l’utilisateur
  • Vérifier ses droits
  • Convertir l’objet d’un format vers un autre

Cela fait déjà pas mal, et encore.

Généralement quand cela commence comme ça sur les projets, on se retrouve avec des services un peu fourre-tout de plusieurs centaines de lignes.

Plusieurs méthodes qui font la même chose, le code est dupliqué de toute part, les classes deviennent de plus en plus lourdes…

À maintenir c’est très compliqué.

✅ Code PHP qui respecte le principe de responsabilité unique (SRP)

Reprenons l’exemple ci-dessus.

Plutôt que d’avoir une arborescence de telle sorte.

Services/
└── UserService.php
├── ...

Et ainsi avoir un service énorme par type de données (comme UserService, ImageService, StatsService…) qui contient beaucoup (trop) de codes.

On applique le principe SRP qui va naturellement tendre vers une arborescence plus facile à lire.

Services/
├── ...
└── User
    ├── UserAuthenticatorService.php
    ├── UserFormatterService.php
    ├── UserSessionService.php
    └── UserUpdatorService.php
    ├── ...

Et voici le code séparé par fichier.

⭐️ Pourquoi utiliser le principe de responsabilité unique (SRP) ?

  • Le code est beaucoup plus clair (une classe de 1000 lignes, ce n’est pas clair).
  • Chaque fichier a désormais un rôle qui lui est propre.
  • Tout le monde peut comprendre à quoi servent les classes dans le dossier Services/User grâce à au nommage.
  • Le projet sera beaucoup plus maintenable, plus facile et agréable à faire évoluer.

O : Open/Closed Principle

On commence à rentrer dans le vif du sujet.

Les entités doivent être ouvertes à l'extension et fermées à la modification.

Cela signifie que l’on doit toujours favoriser l’extension du code à sa modification : on ne modifie pas le fonctionnement suivant l’entité à utiliser, on définit une fonction commune.

Souviens-toi de ceci :

Si tu commences à utiliser des instanceof avec des if ou des switch case en fonction d’un type, c’est sans doute que tu es tombé dans le piège.

❌ Code qui ne respecte pas le principe ouvert/fermé (OC)

La plupart des exemples du principe d’ouverture / fermeture que tu trouves sur internet te parlent d’objet, d’entité.

Mais cela s’applique également aux services, modules, fonctions, classes…

Ici je choisis d’afficher un message de bienvenue à mon utilisateur, et peu importe son type, mon action ne doit pas être modifiée pour chaque élément, même si son affichage est différent.

Malheureusement dans ce code, mon action displayWelcomeMessage() se devra de changer au fur et à mesure que j’ajouterai des types d’utilisateurs…

Si je veux rajouter un nouveau type d’utilisateur, disons un affilié, je vais devoir (encore) modifier le service qui fait l’action.

C’est contraire au principe ouvert / fermé.

✅ Code qui respecte le principe ouvert/fermé (OC)

Voici comment nous avons rendu notre code conforme.

  • Ouvert pour l’extension : c’est le cas ci-dessous, on peut étendre le comportement d’une méthode (ici theName()), chaque classe qui l’implémente fait ce qu’elle veut dans sa méthode.
  • Fermé à la modification : on ne change pas le code source de l’action en fonction du paramètre reçu (que j’ai un client, un utilisateur, un affilié, un administrateur ou que sais-je, le code de l’action ne sera pas modifié).

Le choix d’une interface m’a permis de déterminer une action dans le service.

Désormais je n’ai plus à modifier le comportement de mon service à chaque ajout d’entité.

L’interface m’a permis de créer un contrat entre le service et l’objet qui est affiché.

En une ligne :

« Tu veux que je t’affiche ? Pas de problème, implémente juste l’interface NameableInterface ! »

⭐️ Pourquoi utiliser le principe ouvert/fermé (OC) ?

  • Un code beaucoup plus lisible et plus clair.
  • On aura beaucoup moins de bugs ou de comportements bizarres.
  • En termes de maintenabilité, c’est génial, car l’interface et son contrat te permettent de savoir où tu vas.

L : Liskov’s Substitution Principle (LSP)

Le principe de substitution de Barbara Liskov.

Les objets dans un programme doivent être remplaçables par des instances de leur sous-type sans pour autant altérer le bon fonctionnement du programme.

Là, c’est le moment où tu es tenté de quitter ce site à tout jamais.

Et moi aussi d’ailleurs, même si c’est mon site !

Mais reste, on est bien ici, il y a même une newsletter pour les développeurs 🙂


L’idée du principe est que les enfants ne peuvent pas faire plus ou moins que leur parent.

Voici les 4 conditions que tu dois remplir pour être conforme au Liskov’s Substitution Principle.

  • La signature des fonctions (paramètres et retour) doit être identique entre l’enfant et le parent.
  • Les paramètres de la fonction de l’enfant ne peuvent pas être plus nombreux que ceux du parent.
  • Le retour de la fonction doit retourner le même type que le parent.
  • Les exceptions retournées doivent être les mêmes.

C’est assez théorique, mais tout est là.

SOLID : Principe de substitution de Liskov en PHP
En pratique, lorsque l’on utilise une classe abstraite, il est rarement possible de ne pas respecter les principes 1, 2 et 3 car ton langage t’y « obligera ».

En PHP, les exceptions ne peuvent pas être associées à une méthode (contrairement en Java avec throws par exemple), mais on s’égare là !

❌ Code qui ne respecte pas le principe de substitution de Liskov (LSP)

Un exemple parlant est de créer un système pour récupérer des articles en base de données suivant le CMS (comme WordPress, Joomla, MediaWiki…).

Plusieurs choses ne vont pas dans ce code :

  • Pas les mêmes retours de fonction.
  • Des exceptions différentes suivant les enfants.

Heureusement les paramètres en entrée ne changent pas…

✅ Code qui respecte le principe de substitution de Liskov (LSP)

Pour rappel, un enfant (un objet) ne peut pas faire plus ou moins que son parent (une interface ou une classe parente, abstraite ou non).

J’ai déclaré une classe abstraite parente à étendre pour être certain que chacun des enfants me retourne bien une liste d’articles avec une limite en entrée.

Partout dans mon code, je peux donc interchanger WordPress.php et Joomla.php, sans que ça ne casse rien.

Ces deux classes respectent la définition du parent, elles ne font ni plus, ni moins.

On sait à quoi s’attendre en les utilisant.

Suivant ma conception, je pourrais même remplacer toutes les instances de WordPress.php par CMS.php.

C’est là tout le principe de substitution de Liskov !

⭐️ Pourquoi utiliser le principe de substitution de Liskov (LSP) ?

  • Ça évite les bogues où un enfant fait (surtout) plus qu’un parent (plus de paramètres, de retours, d’exceptions levées…).
  • Les mises à jour de code seront plus faciles à organiser (chaque changement du parent induit des changements vers les enfants = sécurité).
  • On gagne niveau lisibilité et qualité, le code est beaucoup plus simple à lire et à (ré-)utiliser.

I : Interface Segregation Principle (ISP)

Voici ce que dit le principe de ségrégation de l’interface.

Aucun client ne devrait être forcé d'implémenter des méthodes / fonctions qu'il n'utilise pas.

En résumé…

Il vaut mieux faire plusieurs petites interfaces qu’une seule grande.

❌ Code qui ne respecte pas le principe de ségrégation des interfaces (ISP)

Imaginons que nous ayons besoin d’afficher des informations dans l’administration, comme des utilisateurs et des commandes.

Les utilisateurs peuvent être mis à jour (le nom, le prénom…), mais pas les commandes !

Une fois que la commande est passée, on ne peut plus la modifier (normal).

Nous avons donc 2 besoins.

  • Afficher les informations de l’objet requêté en front.
  • Envoyer la mise à jour vers le back d’un de nos deux objets.

Nous avons 2 besoins distincts, mais la même interface est utilisée.

serializeToApi() est incluse dans la classe Order même si celle-ci n’en a pas besoin.

Du coup, chaque entité du projet qui implémente EntityInterface devra implémenter la méthode pour être postée via une API…

Même si ce n’est pas le cas.

✅ Code qui respecte le principe de ségrégation des interfaces (ISP)

Nous avons séparé nos 2 besoins en 2 interfaces séparées.

Le principe « Interface Segregation Principle » de SOLID est respecté, on ne passe de contrat qu’avec l’entité qui en a besoin.

La classe n’a pas besoin d’être postée via une API ?

Pas de problème, je n’implémenterai pas l’interface SerializableInterface.

Aussi simple que cela !

⭐️ Pourquoi utiliser le principe de ségrégation des interfaces (ISP) ?

  • Améliorer la qualité du code.
  • Le code est plus modulable, plus ré-utilisable.
  • Éviter les grosses interfaces rendra le code plus facile à lire et à comprendre.
  • On respecte aussi le principe de responsabilité unique.

D : Dependency Inversion Principle (DIP)

Le dernier des principes SOLID stipule :

Une classe doit dépendre de son abstraction, pas de son implémentation.

Autrement dit, on évite de passer des objets en paramètre lorsqu’une interface est disponible.

Passer en paramètre une interface permet d’être certain que l’objet que tu manipules, peu importe son type, aura les bonnes méthodes associées.

Comme tu te poses la question je te réponds :

Non il n’y a aucun mal à passer des objets en paramètres de tes fonctions.

Ce principe s’applique surtout quand tu as une action commune à exercer pour plusieurs objets différents !

Exemple.

❌ Code qui ne respecte pas le principe d’inversion de dépendance (DIP)

Nous avons plusieurs moyens de paiement dans notre projet.

Ils ont une fonction commune qui est le paiement (la méthode pay()).

Pour pouvoir être correctement utilisés, ils doivent avoir cette méthode.

Ici, le problème est qu’aucune des classes n’a de contrat pour s’assurer que la méthode pay() existe bien dans chaque classe.

Dans la méthode goToPaymentPage(), on ne pourra jamais être certain que le paramètre $paymentChoosen a bien une méthode pay().

Si le prochain développeur qui rajoute un moyen de paiement ne nomme pas ses méthodes comme il faut, tout le code plante.

✅ Code qui respecte le principe d’inversion de dépendance (DIP)

Une interface pour rappel, c’est un contrat avec la classe qui l’implémente.

Ici on certifie au programme qu’il trouvera bien la méthode pay() dans chacune des classes.

On passe désormais en paramètre l’interface PaymentInterface.

Chaque objet (PayPal, Stripe…) peut être utilisé en paramètre donc, étant donné qu’il implémente cette interface.

Nous sommes désormais certains que chaque objet passé aura bien une méthode pay() !

En plus de ça, le code est bien plus élégant à lire dans la méthode goToPaymentPage().

De plus, avec 20 moyens de paiement, la lisibilité des paramètres aurait été ingérable.

⭐️ Pourquoi utiliser le principe d’inversion de dépendance (DIP) ?

  • Un code beaucoup plus facile à modifier, on peut ajouter des fonctionnalités sans crainte de bug.
  • Une lisibilité et une qualité de code accrue.

Voilà, c’était le dernier principe de SOLID… 🙂

Combien de développeurs utilisent SOLID au quotidien ?

Un matin, j’ai posé la question aux développeurs qui se lèvent tôt.

Qui utilise SOLID au quotidien en tant que développeur ?

Voici les réponses.

SOLID et son utilisation chez les développeurs
https://twitter.com/alexsoyes/status/1369531720249319425

Très peu de développeurs l’utilisent, et je serais curieux de savoir pourquoi.

Est-ce par manque de temps ? D’apprentissage ? De besoin ? …

Je suis persuadé que SOLID doit être utilisé parmi les développeurs !

Mais pour ça, il doit être compris, et c’est tout l’intérêt de cet article.

Montrer que SOLID n’est pas si difficile que ça à utiliser dans son projet.

Conclusion

J’ai écrit cet article car beaucoup de tutoriels en informatique sur SOLID montrent des exemples d’utilisation avec des objets que je n’utilise jamais.

Des animaux, des voitures, des formes géométriques…

Moi je suis développeur web, et j’ai besoin d’utiliser SOLID pour le web.

Pour bien comprendre un principe il faut avoir des exemples concrets que l’on puisse appliquer dans son domaine.

Sinon c’est trop théorique et ça ne sert pas à grand-chose.

À ce titre, j’espère que tu auras trouvé cet article différent des autres 🙂

Pour moi, l’utilisation de SOLID dans ton quotidien de développeur doit être une habitude.

Ou du moins quelque chose que tu dois garder en tête.

Savoir que ça existe et comprendre comment cela fonctionne est déjà un grand pas.

Si jamais c’est toujours un peu flou de ton côté, n’hésite pas à laisser un commentaire que je vois comment je peux t’aider.

Comprendre SOLID en 10 minutes ?

C’était le but de cet article.

Écrire quelque chose qui soit compréhensible, facile à assimiler et surtout, applicable au quotidien.

Peut-être que tu n’es pas encore très à l’aise avec les interfaces et toute cette abstraction…

Laisse-toi du temps, ça viendra. Mets cet article en favoris ⭐️ et reviens-y plus tard.

Dans quelques semaines il sera sans doute beaucoup plus clair.


Voici des ressources que j’ai trouvées intéressantes pour écrire cet article :

  • https://blog.bitsrc.io/solid-principles-every-developer-should-know-b3bfa96bb688
  • https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898
  • https://medium.com/proximity-labs/solid-principles-using-typescript-5175aa06b583
  • https://dev.to/evrtrabajo/solid-in-php-d8e

Récapitulatif des 5 principes SOLID

En quelques mots pour résumer et finir cet article.

  • (S) Le principe de responsabilité unique te permet de t’assurer qu’une fonction ne fait qu’une seule et unique chose, mais qu’elle le fait bien.
  • (O) Le principe Ouvert/Fermé te demande de ne pas modifier le comportement d’une action en fonction d’un paramètre, mais plutôt d’étendre les capacités dudit paramètre grâce à une fonction définie en amont.
  • (L) Le principe de substitution de Liskov te permet d’interchanger les enfants d’une classe sans que cela ait d’incidence sur l’exécution du code.
  • (I) Le principe de ségrégation des interfaces te demande de séparer les actions le plus possible.
  • (D) Le principe d’inversion de dépendance préconise de passer des abstractions en paramètre (des contrats grâce aux interfaces) plutôt que les objets eux-mêmes.

Si tu as trouvé l’article cool, n’hésite pas à le partager à d’autres développeurs !

❤️ Tu as aimé cet article ?️

J'ai mis un moment à l'écrire... Ce serait top si tu pouvais le partager à la communauté !