SOLID expliqué simplement
SOLID est un acronyme regroupant cinq principes de conception orientée objet formulés par Robert C. Martin : Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation et Dependency Inversion. Ensemble, ils visent un seul objectif : pouvoir faire évoluer le code sans le casser.
Sommaire
Le problème #
SOLID est l’un des acronymes les plus cités en entretien technique et l’un des moins bien maîtrisés en pratique. Beaucoup de développeurs savent réciter les cinq lettres sans être capables de dire, face à un bout de code réel, lequel des cinq principes est violé — ou pire, appliquent les cinq principes partout, systématiquement, produisant du code sur-architecturé pour des besoins qui n’en demandaient pas tant.
SOLID n’est pas une checklist à cocher automatiquement. C’est un ensemble de signaux d’alerte, à reconnaître dans du code qui commence à devenir rigide, difficile à tester ou risqué à modifier.
L’idée générale #
SOLID regroupe cinq principes de conception orientée objet, formulés (et popularisés) par Robert C. Martin. Chacun d’entre eux, à sa manière, poursuit le même objectif : permettre au code d’évoluer sans se casser.
- S — Single Responsibility Principle : une classe ne devrait avoir qu’une seule raison de changer.
- O — Open/Closed Principle : le code doit être ouvert à l’extension, mais fermé à la modification — on ajoute un comportement, on ne réécrit pas l’existant.
- L — Liskov Substitution Principle : une sous-classe doit pouvoir remplacer sa classe mère partout où celle-ci est utilisée, sans changer le comportement attendu par l’appelant.
- I — Interface Segregation Principle : mieux vaut plusieurs interfaces petites et spécifiques qu’une seule grosse interface qui force à implémenter des méthodes inutiles.
- D — Dependency Inversion Principle : les modules dépendent d’abstractions (interfaces), jamais d’implémentations concrètes — le sens des dépendances technique s’inverse vers le métier.
Ces cinq principes ne s’appliquent pas isolément : dans la pratique, ils se renforcent mutuellement. Une classe respectant le SRP est plus facile à étendre sans la modifier (OCP) ; une bonne inversion de dépendance (DIP) s’appuie souvent sur des interfaces fines (ISP).
Analogie du quotidien #
Pensez à un système d’étagères modulaires, comme ceux qu’on assemble soi-même : chaque module (tiroir, porte, tablette) a un rôle précis et se fixe selon un standard commun.
- S : chaque module a une seule fonction — un tiroir range, une porte ferme. On ne conçoit pas un module « tiroir-porte-éclairage » qui fait tout à moitié bien.
- O : on ajoute un nouveau module (une nouvelle tablette) sans redémonter ni redessiner les modules déjà en place.
- L : n’importe quel tiroir standard de la gamme se fixe à la place d’un autre tiroir standard, sans que le meuble ait besoin d’être modifié.
- I : le kit de vis fourni pour fixer une porte ne vous oblige pas à utiliser aussi le kit prévu pour des roulettes que vous n’installerez jamais — chaque kit reste spécifique à son usage.
- D : le plan de montage général ne mentionne jamais une marque de vis précise, seulement un standard de fixation — n’importe quel fournisseur respectant ce standard peut être utilisé.
Diagramme #
Exemple de code #
Un exemple court par principe, en PHP, suffit à voir concrètement ce que chaque lettre change dans le code.
S — Single Responsibility #
// Violation : une classe calcule ET exporte en PDF — deux raisons de changer.
final class Facture
{
public function calculerTotal(array $lignes): float { /* ... */ }
public function exporterEnPdf(array $lignes): string { /* ... */ }
}
// Respect du principe : une responsabilité par classe.
final class CalculateurFacture
{
public function calculerTotal(array $lignes): float { /* ... */ }
}
final class ExporteurFacturePdf
{
public function exporter(array $lignes): string { /* ... */ }
}
O — Open/Closed #
// Violation : chaque nouveau type de client oblige à modifier cette méthode.
final class CalculateurRemise
{
public function calculer(string $typeClient, float $montant): float
{
return match ($typeClient) {
'standard' => $montant * 0.0,
'premium' => $montant * 0.1,
'vip' => $montant * 0.2,
};
}
}
// Respect du principe : ajouter un type de client = ajouter une classe, sans toucher à l'existant.
interface RemiseStrategy
{
public function calculer(float $montant): float;
}
final class RemiseVip implements RemiseStrategy
{
public function calculer(float $montant): float { return $montant * 0.2; }
}
L — Liskov Substitution #
// Violation : Pingouin hérite d'Oiseau mais trahit le contrat voler().
class Oiseau
{
public function voler(): void { /* ... */ }
}
class Pingouin extends Oiseau
{
public function voler(): void
{
throw new LogicException('Un pingouin ne vole pas');
}
}
// Tout code qui manipule un Oiseau générique et appelle voler() casse avec un Pingouin.
// Respect du principe : séparer ce qui n'est pas garanti pour tous les sous-types.
interface Oiseau {}
interface CapableDeVoler
{
public function voler(): void;
}
class Pingouin implements Oiseau {}
class Aigle implements Oiseau, CapableDeVoler
{
public function voler(): void { /* ... */ }
}
I — Interface Segregation #
// Violation : une interface trop large force des implémentations inutiles.
interface Travailleur
{
public function travailler(): void;
public function manger(): void;
}
final class RobotOuvrier implements Travailleur
{
public function travailler(): void { /* ... */ }
public function manger(): void
{
throw new LogicException('Un robot ne mange pas'); // méthode forcée, inutile
}
}
// Respect du principe : deux interfaces distinctes, chacune implémentée si besoin.
interface Travaillable { public function travailler(): void; }
interface Mangeable { public function manger(): void; }
final class RobotOuvrier implements Travaillable
{
public function travailler(): void { /* ... */ }
}
D — Dependency Inversion #
// Violation : le service dépend directement d'une implémentation concrète.
final class NotificationService
{
public function notifier(string $message): void
{
(new EmailSender())->envoyer($message);
}
}
// Respect du principe : le service dépend d'une abstraction, injectée de l'extérieur.
interface Sender
{
public function envoyer(string $message): void;
}
final class NotificationService
{
public function __construct(private Sender $sender) {}
public function notifier(string $message): void
{
$this->sender->envoyer($message);
}
}
Quand appliquer SOLID ? #
- Sur du code métier amené à évoluer souvent — nouvelles règles, nouveaux types de clients, nouveaux moyens de paiement.
- Quand un
switchou une cascade deif/elsegrossit à chaque nouvelle fonctionnalité (signal typique d’une violation d’Open/Closed). - Quand une classe doit être modifiée pour des raisons complètement indépendantes par des équipes différentes (signal typique d’une violation de Single Responsibility).
- Avec retenue sur du code simple et stable : appliquer les cinq lettres systématiquement à un script utilitaire de vingt lignes produit plus d’indirection que de valeur.
Points importants #
- SOLID est un outil de diagnostic, pas un objectif en soi : le but n’est pas d’avoir « du code SOLID », mais du code qui reste facile à faire évoluer.
- S et I se ressemblent : S sépare les responsabilités d’une classe, I sépare les contrats d’une interface — deux granularités du même réflexe.
- D (Dependency Inversion) est le principe le plus structurant à l’échelle d’une architecture entière : c’est lui qui rend possibles la Clean Architecture, l’architecture hexagonale et le couplage faible en général.
- Les cinq principes se retrouvent partout ailleurs sur ce site : le couplage fort vs faible illustre le D, la séparation des responsabilités approfondit le S, et le Strategy Pattern est une application directe de l’Open/Closed.
🐻 À retenir
- ●SOLID n’est pas une checklist à cocher ligne par ligne : c’est un ensemble de signaux d’alerte à reconnaître dans du code qui devient rigide.
- ●Retenez l’objectif commun aux cinq lettres : isoler ce qui change de ce qui reste stable, pour que modifier une chose ne casse pas les autres.
- ●S et I se ressemblent : S sépare les responsabilités d’une classe, I sépare les contrats d’une interface. Ce sont deux granularités du même réflexe.
Questions d'entretien
Peux-tu citer et expliquer brièvement les 5 principes SOLID ?
SOLID est-il toujours pertinent, ou est-ce un principe daté ?
switch qui grossit, une classe qui a plusieurs raisons de changer — ils restent un outil de diagnostic solide, indépendant du langage ou du framework utilisé.