Appuyez sur Entrée pour voir vos résultats ou Echap pour annuler.

5 exemples faciles pour comprendre les principes SOLID

Introduction

Comme vous savez, un logiciel peut être confectionné de plusieurs façons différentes. En fait, une même fonctionnalité peut être codée sous des designs complètement différents et c’est exactement la source du problème qui fait que les projets de développement ratent souvent leur cible en terme d’estimation d’efforts. En effet, bien qu’il y ait une architecture prédéterminée, le développeur est souvent laissé à lui-même lorsque vient le temps d’implémenter la fonctionnalité. Pour faire un comparaison avec le monde de la construction, c’est un peu comme si on laissait le menuisier choisir comment monter les murs et arranger les pièces… vous imaginez le bordel dans une équipe de 10 menuisiers qui travaillent sur une même maison? Heureusement pour eux (et pour nous!), ils ont des standards à respecter. Pour notre part, nous avons les patrons de conceptions et les principes de programmation, ces derniers étant le sujet de cet article.

Les principes SOLID

L’acronyme SOLID a été inventé par Michael Feathers, blogueur et auteur du livre Working Effectively With Legacy Code, mais a été popularisé par Robert C. Martin que, j’espère, vous connaissez déjà (Clean Code, Clean Coder, etc.).

Ces principes de programmation sont la base de tout code qui se veut clair, propre, facilement maintenable et facile à faire évoluer. Lorsqu’on parle « facilité » de maintenance ou d’évolution à propos du code, il faut comprendre que cela signifie que le coût nécessaire pour effectuer un changement à l’application devrait toujours être inférieur aux bénéfices directement apportés par le changement. Je vous propose donc ici des outils pour vous aider à produire ce type de code.

Single Responsibility Principle (SRP)

Une classe ne devrait avoir qu’une seule raison de changer.

Le SRP ou principe de la responsabilité unique est probablement le plus connu et le plus simple à comprendre, puisqu’il est étroitement lié à la cohésion. Le SRP est aux principes orientés-objet ce que le Singleton est aux patrons de conception. En gros, pour savoir si une classe respecte le SRP, il faut dire: « La classe X fait … » en étant le plus spécifique possible. Si la phrase ci-dessus contient des et ou des ou, alors votre classe a plus d’une responsabilité. De façon plus subtile, si elle contient des mots génériques comme gère ou objet (exemple: gère les utilisateurs, valide les objets), alors vous devriez avoir la puce à l’oreille.

Par contre, il ne faut pas pousser le concept trop loin. Par exemple, une classe qui formate le numéro de téléphone d’un utilisateur peut être un peu trop spécifique.

Exemple

Cette classe encapsule les données d’une utilisateur:

Cette classe encapsule les données d’une utilisateur et valide les informations:

Open/Closed Principle (OCP)

Les entités d’un logiciel devraient être fermées aux modifications mais ouvertes à l’extension.

L’OCP ou principe ouvert/fermé est beaucoup moins compris que le premier. Lorsqu’on dit qu’une classe est fermée aux modifications et ouverte à l’extension, il faut comprendre qu’il est préférable de créer une sous-classe ou d’ajouter des membres pour lui apporter une modification d’état ou de comportement que de modifier les membres existants.

Donc, si vous devez modifier le corps d’une méthode, sa signature ou encore le type d’une propriété d’une classe, vous avez de très forte chance d’être en train de briser l’OCP.

Exemple

Voici une classe qui applique des validations sur l’âge d’un utilisateur:

Si nous voulons ajouter de l’information concernant la province et qu’on ne respecte pas l’OCP, on peut arriver avec:

Ce qui est mal avec cet exemple, c’est que nous avons modifié la signature de la fonction, alors nous aurons assurément des problèmes à la compilation. Au contraire, si on veut respecter l’OCP, on y aurait été avec une solution plus élégante:

Liskov Substitution Principle (LSP)

Si B et C sont des implémentations de A, alors B et C doivent pouvoir être inter-changées sans affecter l’exécution du programme.

L’LSP ou principe de substitution de Liskov est très simple, mais son nom fait un peu peur. En effet, il faut comprendre que les classes partageant une même classe parente partagent aussi un état et/ou un comportement. Si un comportement est défini dans une classe et qu’une autre en hérite, alors elle doit se conformer. Si ce comportement n’est pas désiré dans la classe, alors le LSP n’est pas respecté.

Exemple

Ici, le fait de lancer une exception à l’appel d’une méthode est un indicatif. Il va de même pour les méthodes héritées d’une interface ou d’une classe abstraite qui ne ferait que lancer une exception. En général, c’est un signe que le modèle de données a un problème. Il est vrai qu’un utilisateur anonyme est un utilisateur, mais il est faux que tous les utilisateurs ont des adresses courriel.

Interface Segregation Principle (ISP)

L’appelant ne devrait pas connaître les méthodes qu’il n’a pas à utiliser.

L’ISP ou principe de ségrégation de l’interface est un autre principe nébuleux que nous allons démystifier. Le couplage est un principe orienté-objet qui se quantifie en déterminant les cas d’utilisation des classes. On dit de deux classes qu’elles ont un couplage fort lorsqu’elles s’utilisent. Afin de diminuer le couplage, il est possible de dépendre d’une abstraction, cachant ainsi l’implémentation à l’appelant. Le problème avec l’ISP, c’est lorsqu’une interface ou une classe abstraite devient trop volumineuse et expose trop d’information via son API.

On peut comprendre que l’ISP est lié avec le SRP par le fait que l’interface peut définir plusieurs concepts qui ne sont pas nécessairement liés.

Exemple

Dans l’exemple ci-dessous, la classe Vehicle a deux comportements. Nous pouvons démarrer la voiture ou allumer les lumières.

En supposant qu’une deuxième classe aurait la responsabilité d’allumer les lumières comme suit:

Afin de respecter au maximum l’ISP, il faut se demande pourquoi une classe responsable d’allumer les lumières devrait savoir qu’un véhicule peut être démarrer. On peut alors extraire l’interface afin que notre classe ne connaisse que ce dont elle a besoin:

Dependency Inversion Principle (DIP)

Les modules d’une application devraient dépendre d’abstractions.

Le DIP ou principe d’inversion de dépendance nous dit que les dépendances d’une classe ne devraient jamais être concrètes. Puisqu’elle ne doit pas connaître l’implémentation de ses dépendances, nous pouvons nous assurer du respect de ce principe en implémentant le patron de conception d’injection de dépendances, soit via le constructeur, soit via les mutateurs.

Supposons que vous avez une classe responsable de la journalisation. Cette classe est utilisée dans un service afin de journaliser les entrées et sorties:

Ici, Logger et SomeService sont fortement couplées. Le problème surgira lorsqu’il faudra journaliser dans un fichier au lieu de la console, surtout si Logger est utilisée comme telle partout dans l’application. Par contre, si nous dépendons d’une interface:

Malheureusement, nos classes restent encore couplées. Pour sortir l’implémentation complètement, on doit inverser les dépendances en implémentant l’injection:

Et voilà! SomeService n’a plus connaissance de la technologie utilisée pour la journalisation.

Conclusion

Les principes SOLID sont un outil puissant pour un développeur s’il prend le temps de les comprendre. Lorsqu’ils deviennent une habitude, le code devient alors beaucoup plus simple et facile à maintenir. Par contre, il faut comprendre que ces principes ne doivent pas devenir une religion à laquelle on adhère aveuglément sans les remettre en question. Ce sont des principes qu’il faut respecter, mais à pas à n’importe quel prix.

Utilisez la zone de commentaires ci-dessous pour me donner votre opinion! N’hésitez pas à partager et réagir sur les médias sociaux.

Suivez-nous par courriel!

Saisissez votre adresse courriel pour vous abonner au blog d'Ezo et recevoir une notification de chaque nouvel article par email.