Concept #009
ArchitectureLes 5 Principes SOLID
- architecture
- clean code
- design patterns
- SOLID
Les principes SOLID en une phrase
SOLID est un acronyme représentant 5 principes fondamentaux de la programmation orientée objet, formulés par Robert C. Martin (Uncle Bob), qui guident les développeurs vers un code plus robuste, plus flexible et plus facile à maintenir.
Souvent cités, rarement maîtrisés. Ce ne sont pas des dogmes religieux, mais des outils puissants pour éviter que votre base de code ne devienne un plat de spaghettis géant.
S — Single Responsibility Principle (SRP)
Une classe ne devrait avoir qu'une seule et unique raison de changer.

Le problème : la "God Class"
On l'a tous vu (ou écrit) : cette classe "couteau suisse" qui gère à la fois la logique métier, la persistance en base de données, l'envoi de notifications Slack, la comptabilité et le nettoyage de la base. C'est le fameux SUPER_MANAGER.
Le souci ? Si une seule de ces responsabilités change, tout risque de casser. Modifier l'envoi de mails peut introduire un bug dans la logique métier. Un changement de base de données impacte l'affichage. Tout est couplé.
La solution : séparer le "QUOI" du "COMMENT"
Pensez à un manager qui délègue : le Use Case gère le QUOI (la logique métier), et les adaptateurs gèrent le COMMENT (persistance, affichage, notifications). Chacun son job.
Concrètement, séparez en 3 responsabilités distinctes :
- Le Métier (Service Métier) — Ne gère que les règles business
- La Persistance (Repository) — Ne gère que le stockage des données
- L'Affichage (Presenter / View) — Ne gère que la mise en forme pour l'utilisateur
Indicateur de violation
Si vous décrivez ce que fait votre classe et que vous utilisez le mot "ET", c'est un signal d'alarme : "Cette classe gère les utilisateurs ET envoie des emails ET génère des rapports" → 3 classes distinctes.
O — Open/Closed Principle (OCP)
Ouvert à l'extension, fermé à la modification.

Le problème : le pied-de-biche dans le code stable
Chaque fois qu'on veut ajouter une fonctionnalité, on "ouvre" le code existant au pied-de-biche pour y insérer la nouvelle feature. Modifier du code stable qui fonctionne, c'est risquer de casser des fonctionnalités existantes en production.
La solution : les points d'extension
Au lieu de modifier le code existant, on crée un point d'extension (une interface) et on branche un nouveau module sans toucher au code stable.
Le Cœur du Système (Fermé à la modification) :
- Le module stable (Core) est verrouillé, on n'y touche plus
- Il expose un point d'extension via une interface
L'Extension (Ouverte) :
- On ajoute simplement un nouveau module qui implémente l'interface
- Exemple : un système de paiement avec une interface
PaymentProcessor. Pour ajouter le paiement Crypto, on crée un nouveau moduleCryptoPaymentsans modifierPaypalPaymentniCardPayment
En pratique
Utilisez le Strategy Pattern, le Plugin Pattern ou l'injection de dépendances pour rendre votre code extensible sans modification. Pensez aux plugins de votre IDE : on en ajoute sans jamais modifier le code de l'éditeur lui-même.
L — Liskov Substitution Principle (LSP)
Les sous-classes doivent être interchangeables avec leur classe parente sans mauvaise surprise.

Le problème : les mauvaises surprises
Si le contrat parent dit "Machine à Café", la classe enfant n'a pas le droit de servir du thé. Le code appelant s'attend à recevoir un café de toute machine qui respecte ce contrat. L'héritage est une promesse de comportement, pas juste du partage de code.
L'analogie de la machine à café
- Le contrat de base :
MachineACafé— "Je te sers un café quand tu appuies sur ce bouton" - Violation :
MachineAThéhérite deMachineACafémais sert du thé → le code qui attend un café est cassé - Respect :
MachineAEspressohérite deMachineACaféet sert bien un café (espresso) → le contrat est respecté
La règle d'or
Une sous-classe ne doit jamais :
- Renforcer les préconditions (exiger plus que la classe parente)
- Affaiblir les postconditions (promettre moins que la classe parente)
- Lever des exceptions inattendues que la classe parente ne lève pas
Si vous devez écrire if (instance is SubClass) dans le code appelant, c'est que LSP est violé. Le polymorphisme devrait suffire.
I — Interface Segregation Principle (ISP)
Préférez plusieurs interfaces spécifiques plutôt qu'une seule interface généraliste.

Le problème : l'interface fourre-tout
Pourquoi forcer une classe Poisson à implémenter une méthode voler() qu'elle n'utilisera jamais ? Une interface universelle "tout-en-un" (voler, nager, imprimer, manger, calculer, danser...) force les clients à dépendre de méthodes qu'ils n'utilisent pas. C'est du gaspillage et une source de bugs.
La solution : la ségrégation
Au lieu d'une méga-interface monolithique, découpez en interfaces ciblées :
- Interface Volant — Battement des ailes, direction → Ne gère que le vol
- Interface Nageur — Gouvernail, nageoires → Ne gère que la nage
- Interface Calculateur — Opérations mathématiques → Ne gère que le calcul
Chaque classe n'implémente que les interfaces dont elle a réellement besoin. Un poisson implémente Nageur mais pas Volant. Un oiseau implémente Volant mais pas Nageur (sauf le canard, qui fait les deux).
En pratique
Quand une interface grossit, posez-vous la question : "Est-ce que tous les implémenteurs utilisent toutes les méthodes ?" Si non, il est temps de découper. En TypeScript/Java, préférez composer plusieurs petites interfaces plutôt qu'en hériter une seule grosse.
D — Dependency Inversion Principle (DIP)
Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d'abstractions.

Le problème : le couplage fort
Imaginez un pilote qui, pour tourner, doit "tirer le câble B42 et ajuster la valve vapeur". Ce pilote connaît trop de détails du moteur spécifique. Impossible de changer de véhicule sans réapprendre tout le fonctionnement interne.
C'est exactement ce qui se passe quand votre logique métier dépend directement de votre base de données, de votre framework HTTP ou de votre service d'envoi de mails.
La solution : l'inversion de dépendance
Introduisez une abstraction (interface) entre le haut niveau et le bas niveau :
- Le Haut Niveau (Le Pilote) — "Je connais le volant et les pédales. Le reste, je m'en fiche !" → Dépend de l'interface
Commandes - L'Abstraction (Le Contrat Standard) — Interface
VéhiculeConduisible→ Le contrat que tout véhicule doit respecter - Le Bas Niveau (Les Véhicules) — Voiture, tracteur, hovercraft → Les constructeurs s'adaptent au standard pour être pilotables
Pourquoi "inversion" ?
Sans ce principe, la dépendance va naturellement du haut vers le bas : Service → BaseDeDonnées. Avec DIP, on inverse : Service → Interface ← BaseDeDonnées. Les deux pointent vers l'abstraction. Résultat : on peut changer la base de données sans toucher au service.
En pratique
C'est le fondement de l'Architecture Hexagonale (Ports & Adapters) et du Clean Architecture. Votre domaine métier est au centre, protégé par des interfaces (ports). Les détails techniques (base de données, API, UI) sont à l'extérieur (adapters) et peuvent être remplacés à volonté.
Résumé : les 5 principes à retenir
| Principe | Acronyme | En une phrase |
|---|---|---|
| Single Responsibility | SRP | Une classe = une seule raison de changer |
| Open/Closed | OCP | Ouvert à l'extension, fermé à la modification |
| Liskov Substitution | LSP | Les sous-classes respectent le contrat du parent |
| Interface Segregation | ISP | Plein de petites interfaces > une grosse |
| Dependency Inversion | DIP | Dépendez des abstractions, pas des détails |
Quand appliquer SOLID ?
Ces principes ne sont pas à appliquer aveuglément sur chaque ligne de code. Ils prennent tout leur sens dans les projets qui grandissent, où la maintenabilité et l'évolutivité deviennent critiques. Pour un script de 50 lignes, SOLID serait de la sur-ingénierie. Pour une application métier qui vivra des années, c'est la différence entre un code qui évolue sereinement et un code qu'on a peur de toucher.