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

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 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é !