Aller au contenu principal
LaPolaris lance ses formations. Jusqu'à 40% de réduction En savoir plus

Les principes SOLID : le socle du code orienté objet maintenable

Les principes SOLID structurent la conception orientée objet depuis plus de vingt ans. SRP, OCP, LSP, ISP, DIP : cinq règles distinctes qui, appliquées ensemble, rendent le code modulaire, testable et résistant au changement. Tour d'horizon complet avec exemples concrets.

Architecture logicielle ·
Adel LATIBI
Adel LATIBI
Les principes SOLID : le socle du code orienté objet maintenable

Publiés par Robert C. Martin au tournant des années 2000, les cinq principes SOLID restent aujourd'hui une référence incontournable pour quiconque développe en orienté objet. Loin d'être une check-list théorique, ils répondent à des problèmes concrets que tout développeur finit par rencontrer : du code rigide, impossible à tester, qui se brise dès qu'on touche à une dépendance.

Pourquoi parler encore de SOLID en 2026 ?

Le monde du développement avance vite. Les frameworks changent, les langages évoluent, les architectures se réinventent. Et pourtant, les mêmes erreurs de conception réapparaissent dans les bases de code, qu'elles soient en PHP, Python, Java ou TypeScript. Des classes qui grossissent sans raison, des modules que l'on ne peut pas tester sans instancier la moitié de l'application, des interfaces qui imposent des méthodes dont personne ne veut.

C'est précisément ce que SOLID cherche à corriger. Le sigle regroupe cinq principes distincts : SRP (Single Responsibility Principle), OCP (Open/Closed Principle), LSP (Liskov Substitution Principle), ISP (Interface Segregation Principle) et DIP (Dependency Inversion Principle). Pris ensemble, ils forment une philosophie de conception qui rend le code plus modulaire, plus testable et plus résistant au changement.

Chacun mérite qu'on s'y attarde sérieusement, sans se contenter d'une définition en une ligne.

SRP

Single Responsibility Principle : une classe, une raison de changer

Le principe de responsabilité unique est souvent le premier qu'on cite et le premier qu'on viole. Sa formulation originale est trompeusement simple : une classe ne doit avoir qu'une seule raison de changer. Ce que Robert Martin précise par la suite, c'est que cette "raison" renvoie à un acteur métier, pas à une fonction technique. Une classe UserService qui gère à la fois l'authentification, l'envoi d'e-mails de bienvenue et la mise à jour du profil ne respecte pas SRP, parce que trois équipes différentes pourraient vouloir la modifier pour des raisons qui n'ont rien à voir entre elles.

En pratique, on reconnaît une violation de SRP quand une classe dépasse quelques dizaines de lignes et accumule des dépendances vers des services qui n'ont pas de lien direct. La correction consiste à extraire des responsabilités dans des classes spécialisées : AuthService, WelcomeMailer, ProfileUpdater. Chacune devient testable indépendamment et peut évoluer sans perturber les autres.

Un indicateur concret : si vous avez du mal à nommer une classe avec un nom précis et que vous êtes tenté d'utiliser des termes vagues comme Manager, Handler ou Util, c'est souvent le signe que plusieurs responsabilités se sont accumulées.

SRP améliore directement la lisibilité du code. Une classe qui fait une seule chose se comprend en quelques minutes. C'est aussi ce qui rend les revues de code plus rapides et les corrections de bugs moins risquées.

OCP

Open/Closed Principle : ouverts à l'extension, fermés à la modification

Le principe ouvert/fermé, formulé initialement par Bertrand Meyer et repris par Martin, pose qu'un composant logiciel doit pouvoir être étendu sans être modifié. Dit autrement : ajouter une nouvelle fonctionnalité ne doit pas obliger à toucher au code existant et donc à risquer de casser ce qui fonctionne déjà.

Un exemple classique : une fonction qui calcule le prix d'une commande avec une suite de conditions if pour chaque type de remise. Chaque fois qu'un nouveau type de remise arrive, il faut modifier cette fonction, relire la logique existante, s'assurer qu'on ne régresse rien. C'est fragile et chronophage.

La solution OCP consiste à définir une abstraction (une interface ou une classe abstraite Discount avec une méthode apply()) et à laisser chaque type de remise l'implémenter. Ajouter une remise saisonnière revient à créer une nouvelle classe, sans toucher à l'orchestrateur.

Ce principe est directement lié au concept de polymorphisme. Il s'appuie aussi sur les patterns de conception comme la stratégie ou le décorateur, qui permettent de varier les comportements sans toucher aux structures existantes.

LSP

Liskov Substitution Principle : la substitution sans surprise

Formulé par Barbara Liskov en 1987, ce principe touche à l'héritage. Il stipule que si une classe B hérite d'une classe A, alors tout endroit du code qui utilise A doit pouvoir utiliser B sans que le comportement global change.

L'exemple le plus pédagogique reste celui du carré et du rectangle. Mathématiquement, un carré est un rectangle. En programmation, si Square hérite de Rectangle et que Rectangle expose des méthodes setWidth() et setHeight() indépendantes, le carré ne peut pas les respecter sans incohérence (modifier la largeur doit aussi modifier la hauteur). Substituer un Square à un Rectangle produit alors des comportements inattendus.

LSP invite à questionner chaque relation d'héritage. L'héritage doit modéliser une relation "est un" au sens comportemental, pas seulement taxonomique. Quand les comportements divergent, la composition est souvent préférable.

En termes concrets, une violation de LSP se manifeste souvent par des if instanceof dans le code client, par des méthodes surchargées qui lancent des exceptions inattendues, ou par des préconditions renforcées dans les sous-classes. Ce sont des signaux clairs que la hiérarchie est mal conçue.

ISP

Interface Segregation Principle : des interfaces taillées pour leurs utilisateurs

ISP part d'un constat simple : une interface trop large force ses implémenteurs à définir des méthodes dont ils n'ont pas l'utilité. Une classe qui implémente une interface de vingt méthodes mais n'en utilise que quatre se retrouve à écrire du code vide, ou pire, à lever des exceptions dans les méthodes non supportées, ce qui viole LSP au passage.

La solution est de découper les interfaces selon les besoins réels de leurs clients. Plutôt qu'une interface Animal avec walk(), swim(), fly() et speak(), on préférera des interfaces ciblées : Walkable, Swimmable, Flyable. Un canard implémenterait Walkable et Swimmable sans être contraint de définir fly().

ISP s'applique aussi à la conception d'API et de services. Un endpoint qui renvoie trente champs alors qu'un client n'en exploite que quatre est une violation de l'esprit du principe. La notion de "client" au sens large inclut tout consommateur d'une interface, qu'il s'agisse d'une classe, d'un module ou d'un service externe.

En PHP ou TypeScript, ce principe se traduit directement dans la façon de déclarer les interfaces. Des interfaces étroites et composables valent mieux qu'une interface monolithique difficile à faire évoluer.

DIP

Dependency Inversion Principle : dépendre des abstractions, pas des implémentations

DIP est le principe qui clôt et unifie les quatre autres. Il pose deux règles. La première : les modules de haut niveau ne doivent pas dépendre des modules de bas niveau ; les deux doivent dépendre d'abstractions. La seconde : les abstractions ne doivent pas dépendre des détails, ce sont les détails qui dépendent des abstractions.

Sans DIP, une couche métier qui instancie directement un MySQLRepository est liée à une technologie concrète. Changer de base de données, passer à une version en cache ou écrire des tests unitaires sans base de données réelle devient un chantier. Avec DIP, la couche métier déclare une dépendance vers une interface UserRepositoryInterface. L'implémentation concrète est injectée depuis l'extérieur, souvent via un conteneur d'injection de dépendances.

C'est ce principe qui rend possible les tests unitaires sans infrastructure réelle. En injectant un mock ou un stub à la place de l'implémentation concrète, on isole parfaitement la logique métier et on teste uniquement ce qui doit l'être.

En Symfony, le conteneur de services est une concrétisation directe de DIP. On déclare des interfaces dans les typages des constructeurs, et Symfony résout les implémentations selon la configuration. En Python, l'injection de dépendances peut être gérée manuellement ou via des bibliothèques comme dependency-injector.

DIP renforce tous les autres principes. Une architecture qui respecte SRP, OCP, LSP et ISP mais qui câble ses dépendances en dur reste difficile à tester et à faire évoluer. L'inversion des dépendances est ce qui donne à ces principes leur pleine portée.

Comment appliquer SOLID sans tomber dans l'excès

SOLID n'est pas une liste d'obligations à respecter mécaniquement dans chaque ligne de code. L'appliquer sans discernement peut produire des architectures sur-découpées, avec des dizaines d'interfaces pour des objets qui n'évoluent jamais. Le principe de conception à garder en tête est celui de la pertinence : ces règles ont une valeur réelle quand elles répondent à une contrainte concrète.

Une bonne pratique courante consiste à ne pas anticiper l'abstraction. On commence par une implémentation simple et directe. Lorsqu'une deuxième variante apparaît, c'est le moment d'extraire une interface. Lorsqu'une classe commence à accumuler des responsabilités distinctes et que ses tests unitaires nécessitent trop de mocks, c'est le signal pour la découper. SOLID guide des refactorisations, autant qu'il oriente des designs initiaux.

Les projets qui bénéficient le plus de ces principes sont ceux qui durent : les plateformes avec plusieurs années de maintenance, les codebases avec plusieurs développeurs qui interviennent en parallèle, les applications dont les spécifications évoluent régulièrement. Pour un script jetable ou un prototype de validation, appliquer SOLID à la lettre serait disproportionné.

Il est aussi utile de les coupler à d'autres pratiques : les tests automatisés révèlent les violations de SRP et DIP mieux que n'importe quelle revue de code. Le refactoring continu est le contexte naturel dans lequel ces principes s'appliquent progressivement, au rythme des besoins réels du projet.

Tableau récapitulatif des cinq principes

Principe Sigle Ce qu'il prescrit Signal de violation
Single Responsibility SRP Une classe, une raison de changer Classe trop large, nommée vaguement
Open/Closed OCP Étendre sans modifier le code existant Longues chaînes de conditions selon le type
Liskov Substitution LSP Les sous-types sont substituables sans surprise Vérifications instanceof, exceptions inattendues
Interface Segregation ISP Interfaces ciblées, pas de méthodes inutiles Méthodes vides ou non supportées
Dependency Inversion DIP Dépendre des abstractions, pas des détails Instanciation directe de classes concrètes

SOLID et les autres standards du génie logiciel

SOLID ne fonctionne pas en silo. Ces principes se combinent naturellement avec d'autres pratiques et concepts fondateurs du génie logiciel. Le principe DRY (Don't Repeat Yourself) complète SRP : deux responsabilités distinctes ne doivent pas non plus partager du code dupliqué. Le principe YAGNI (You Aren't Gonna Need It) tempère SOLID en rappelant qu'anticiper des abstractions dont on n'a pas encore besoin génère de la complexité inutile.

Les design patterns GoF (Gang of Four) sont souvent les outils concrets qui permettent de mettre en oeuvre SOLID. Le pattern Strategy répond à OCP. Le pattern Repository répond à DIP. Le pattern Decorator permet d'étendre des comportements sans modifier les classes de base. Ces patterns ne sont pas des recettes à appliquer sans réfléchir, mais des solutions connues à des problèmes récurrents qui s'alignent naturellement avec les principes SOLID.

Dans le contexte de l'architecture logicielle plus large, SOLID prépare bien le terrain pour des approches comme le Domain-Driven Design (DDD) ou l'architecture hexagonale (Ports and Adapters), qui séparent strictement la logique métier de l'infrastructure. DIP, en particulier, est le mécanisme central de l'architecture hexagonale.

Ce qu'il faut retenir

Les principes SOLID ne sont pas une doctrine à adopter en bloc du jour au lendemain. Ce sont des outils de diagnostic autant que de conception. Ils aident à nommer des problèmes qu'on ressent intuitivement : ce module est trop difficile à tester, cette classe change trop souvent pour de mauvaises raisons, cet héritage produit des comportements étranges.

Les maîtriser, c'est se donner un vocabulaire commun avec les autres développeurs et une grille de lecture pour évaluer la qualité d'un design. C'est aussi comprendre pourquoi certains frameworks sont conçus comme ils le sont, et comment les utiliser au mieux de leur philosophie.

Questions fréquentes sur les principes SOLID

SOLID s'applique-t-il uniquement aux langages orientés objet ?

SOLID a été conçu dans un contexte orienté objet et ses formulations originales supposent des classes, des interfaces et de l'héritage. Cependant, plusieurs principes s'adaptent à d'autres paradigmes. SRP et DIP notamment ont des équivalents naturels en programmation fonctionnelle : des fonctions qui font une seule chose, et des dépendances passées en paramètre plutôt qu'instanciées en dur. Les langages comme Python ou JavaScript, qui mêlent plusieurs paradigmes, bénéficient largement de ces principes même sans framework orienté objet strict.

Quel principe SOLID est le plus difficile à appliquer en pratique ?

LSP est souvent cité comme le plus subtil, car il exige de distinguer la relation taxonomique (un carré est un rectangle au sens mathématique) de la relation comportementale (un carré ne peut pas se substituer à un rectangle au sens programmatique). OCP est quant à lui le plus difficile à calibrer : trop anticiper les points d'extension crée une complexité artificielle, pas assez et on se retrouve à modifier du code existant à chaque évolution. La juste mesure dépend du contexte et de l'expérience.

SOLID est-il compatible avec une architecture microservices ?

Oui, et les deux se complètent. À l'intérieur d'un microservice, SOLID guide la conception des classes et des modules. À l'échelle de l'architecture, des principes similaires s'appliquent : chaque service a une responsabilité délimitée (SRP à grande échelle), les services communiquent via des contrats d'interface (ISP et DIP), et l'ajout d'un nouveau service ne doit pas nécessiter de modifier les services existants (OCP). L'architecture microservices peut être vue comme une application de SOLID au niveau système.

Comment savoir si mon code viole un principe SOLID sans expertise avancée ?

Plusieurs indicateurs pratiques signalent des problèmes. Une classe difficile à nommer précisément (recours à des termes comme Manager ou Helper) suggère une violation de SRP. Un test unitaire qui nécessite de configurer cinq dépendances pour tester une seule méthode pointe vers un problème de DIP. Une méthode héritée qui doit vérifier le type réel de l'objet avant d'agir indique une violation de LSP. L'écriture de tests est le meilleur révélateur : un code qui se teste difficilement en isolation est presque toujours un code qui viole un ou plusieurs principes SOLID.

Faut-il connaître SOLID pour utiliser Symfony ou Laravel ?

On peut utiliser ces frameworks sans connaître SOLID explicitement, mais les comprendre aide à mieux les utiliser. Symfony est architecturé autour de DIP (conteneur de services, injection de dépendances), OCP (extension via les EventListeners et les CompilerPass) et ISP (interfaces métier dans les contrats). Laravel s'appuie sur des patterns similaires avec ses Service Providers et ses Facades. Comprendre SOLID donne du sens aux choix de conception de ces frameworks et permet d'en tirer le meilleur parti dans ses propres développements.

SOLID est-il toujours pertinent avec les pratiques modernes comme le TDD ou le Clean Architecture ?

Absolument. SOLID est en fait une des fondations sur lesquelles reposent ces approches. Le TDD (Test-Driven Development) est difficile à pratiquer sur un code qui viole DIP et SRP, car les dépendances concrètes rendent les tests lents et fragiles. La Clean Architecture de Robert Martin est elle-même une systématisation de DIP à grande échelle, avec des couches qui dépendent toutes vers un centre (le domaine métier) via des interfaces. Ces pratiques ne remplacent pas SOLID, elles en sont des applications et des extensions naturelles.

Vous êtes expert ?

Partagez votre expertise sur notre blog

Tutoriel, retour d'expérience, analyse — publiez un article invité et gagnez en visibilité.

Écrire pour nous