Le principe SOLID

SOLID… Il est solide mon code ! C’est un roc !… C’est un pic !… C’est un cap !… Que dis-je, c’est un cap ?… C’est une péninsule !

Nous n’allons pas parler de solidité mais d’un concept : le principe SOLID (S.O.L.I.D). C’est un acronyme composé de 5 lettres, de 5 principes de bases pour la Programmation Orientée Objet (POO).

En suivant ces principes, cela vous permettra de développer une application maintenable, plus fiable et plus robuste (ce n’est pas SOLID pour rien !).

Single Responsability

Que ce soit une classe ou une méthode, il est important que celle-ci ne puisse faire qu’une et une seule chose : la responsabilité unique.

Prenons l’exemple d’une machine à café : son unique utilité est de préparer du café. Si vous souhaitez de la bière, ce n’est pas la bonne machine.

“Moi j’ai une machine qui fait les deux”

Eh bien… ce n’est pas une machine à café, c’est une machine tout court (ou autre), mais pas à café. Je fais cette précision car la précision du terme est important. Ce principe va vous obliger à minimiser vos classes ou vos services et donc éviter ce que l’on appelle une God Class.

Pour pousser ce principe au maximum, il faut que vous réduisiez le plus possible les fonctionnalités de vos classes.

Typiquement, pour notre machine à café, nous ne pouvons pas avoir une classe qui chauffe l’eau et qui, en même temps, fasse couler le café. Ce n’est pas la même fonctionnalité !

Et si vous n’êtes pas sûr de ce que vous faites ou si vous pensez ne pas avoir suffisamment réduit la responsabilité de votre classe, n’hésitez pas à faire relire votre code !

Open / Closed

Une classe doit être ouverte à l’extension et fermée à la modification. Dans les faits, c’est très simple : pourquoi voudriez-vous modifier un code source qui fonctionne ? Mis à part pour la correction d’un bug… Mais, nous sommes tous d’accord pour dire que notre code ne bug jamais ? Non ?

Si vous respectez le premier principe, vous allez considérablement réduire la densité de vos classes ainsi que la taille de vos méthodes. Vous allez multiplier les fichiers mais par expérience, je vous garantis qu’il est plus agréable d’avoir beaucoup de fichiers avec peu de lignes de code qu’un fichier de 1 000 lignes à maintenir (oui 1 000 lignes c’est 10 fois trop. J’insiste : 10 fois trop, allez… 6 ou 7 fois)

L’idée est donc de ne pas toucher à ce qui fonctionne. Grâce à l’objet vous avez différentes méthodes pour améliorer / étendre votre classe ou votre méthode :

  • L’héritage
  • Les design patterns dis “de structure”
  • La surcharge

Ne serait-ce qu’avec toutes ces solutions, vous pouvez ouvrir une classe à l’extension sans avoir à la modifier !

En même temps, vous vous voyez modifier votre machine à café pour en faire une machine à bière ? Non, ça n’a pas de sens ! Et, en plus, on appellerait ça du bricolage.

Liskov Substitution

On aurait pu trouver plus simple comme nom... Il nous vient de Barbara Liskov qui nous affirme que :

Si q(x) est une propriété démontrable pour tout objet x de type T, alors q(y) est vraie pour tout objet y de type S tel que S est un sous-type de T.

Pour illustrer cette phrase, vous entendrez régulièrement l’exemple suivant : “Un carré est un rectangle” (qui est bien vrai mathématique). Si l’on suit rigoureusement le principe d’héritage dans la programmation objet qui se base sur la formule : “est un”, vous allez avoir envie de faire une classe Carré qui hérite de la classe Rectangle.

Erreur ! il ne faut surtout pas car vous allez vous heurter à des problèmes de conception. Un carré ne se comporte pas comme un rectangle d’un point de vue objet pur. Quand vous allez modifier le côté d’un carré, la largeur et la hauteur vont se confondre, ce qui n’est pas vrai avec un rectangle. De fait, lorsque le carré va hériter des getters et des setters du rectangle, vous allez devoir changer fondamentalement le fonctionnement de base (la largeur, et la hauteur).

Pour résumer, le principe de Liskov vous met en garde sur ces concepts. Dans les actions d’héritages, de surcharge, de l’utilisation des propriétés de vos classes en général, il faut faire attention à ce que vos objets gardent les mêmes fondamentaux.

Interface segregation

Souvent, j’entends des gens me demander : “Mais, à quoi sert une interface ?”. Quand on ne connait pas son intérêt, on a le sentiment d’écrire un fichier assez inutile aux premiers abords. Pourquoi définir des méthodes dans une fichier qui donne l’impression d’être vide ?

Une interface est censé vous permettre de “contractualiser”, d’obliger un objet à utiliser certaines fonctionnalités.

Pour illustrer cette explication, prenez l’exemple d’une machine Nespresso. Pour faire votre café, cette machine accepte des capsules Nespresso mais pas seulement ! Pour pouvoir prendre en compte d’autres capsules, il faut savoir s’interfacer avec la machine. De fait, lorsque vous mettez votre capsule dans la machine, elle est prise en compte grâce à l’utilisation de la bonne interface. De l’autre côté, la machine attend et accepte une certaine interface, que ce soit du Nespresso ou autre, ce n’est pas son problème, elle saura utiliser la capsule tant qu’elle est conforme à ce que la machine peut recevoir.

Maintenant, qu’est-ce que nous dit la ségrégation des interfaces à travers cette explication ? Eh bien, il est important de grouper correctement ses interfaces. Plus une interface est simpliste, “atomique”, plus il est facile de distinguer les contrats et leurs utilités.

Alors ne faites pas une interface de 50 lignes ! pour le coup, ce serait bien trop long.

Dependency injection

Le dernier principe ! L’injection de dépendance. Un des mes préférés et on le retrouve chez les design patterns.

Il faut injecter les dépendances nécessaires à une classe. Autrement dit, on donne à une classe ce dont elle a besoin pour fonctionner. La question, est de savoir ce qu’est une dépendance ? Reprenons notre machine à café.

Pour pouvoir faire du café avec notre machine… de quoi dépend-t-elle ?

  • D’une capsule de café
  • D’électricité
  • D’eau

Est-ce que la machine sait faire ses 3 choses toute seule ? Non. On lui donne ces 3 dépendances.

L’intérêt de ce principe est de décentraliser la dépendance. En donnant les objets à une classe, cela veut dire que celle-ci peut travailler avec différents objets tant qu’ils respectent le contrat. Vous l’aurez compris, on fait rarement de l’injection de dépendance sans interface ou sans vérifier les types de variable que l’on va recevoir.

Vous pouvez l’effectuer via les constructeurs de classes mais aussi avec des “setters”.

Sachez qu’en décentralisant les dépendances, vous simplifiez grandement votre application. C’est comme si vous définissiez un fichier de configuration pour utiliser vos objets. Quoi de plus abstrait et de maintenable qu’une configuration ?

Terminé ! Je viens donc de vous présenter les 5 principes SOLID de la Programmation Orientée Objet. Je n’ai pas mis d’exemples de code pour éviter de rallonger l’article car je souhaitais vous expliquer de manière écrite et conceptuelle ce que c’est. En revanche, je ferai un article pour présenter chaque principe avec du code à la clé afin de mettre plus en valeur toutes ces explications !