LSP, ISP et DIP : les trois derniers principes
Contrats d’héritage, interfaces minces, inversion.
Objectifs
À la fin de cette leçon, vous saurez :
- formuler Liskov, Interface Segregation et Dependency Inversion ;
- reconnaître la violation DIP dans votre propre code passé ;
- relier ces principes à l'injection de dépendances de NestJS.
L — Liskov Substitution Principle
Un sous-type doit pouvoir remplacer son parent sans casser le programme.
class Oiseau {
voler() { ... }
}
class Pingouin extends Oiseau {
voler() { throw new Error("Je ne vole pas !"); } // violation !
}
Tout code qui fonctionne avec Oiseau peut recevoir un Pingouin... et exploser. L'héritage impose un CONTRAT : le fils ne peut ni restreindre les entrées acceptées, ni lancer des surprises nouvelles. Si le contrat ne colle pas, ce n'est pas un héritage : c'est une composition déguisée.
Test rapide : relisez chaque méthode héritée et demandez « est-ce que je peux tout faire comme avant ? ». Un « non » = violation LSP.
I — Interface Segregation Principle
Ne pas forcer un client à dépendre de méthodes qu'il n'utilise pas.
// Interface obèse
class Travailleur {
travailler() {}
manger() {}
dormir() {}
}
class Robot extends Travailleur {
manger() { throw new Error("N/A"); } // forcé d'implémenter l'inutile
}
Découpage honnête : plusieurs petites interfaces ciblées (Travailleur, Mangeur, Dormeur) plutôt qu'une grosse. En JavaScript sans interfaces natives, cela se lit dans vos classes abstraites ou conventions : n'exigez que ce qui sert. En TypeScript (chapitre 32) ce sera littéralement des interfaces séparées.
D — Dependency Inversion Principle
Dépendre d'abstractions, pas de détails concrets.
Votre code passé violait déjà ça :
class UserService {
constructor() {
this.repo = new UserRepository(); // ⚠️ détail concret figé
}
}
Le service fabrique lui-même sa dépendance : impossible de tester sans vraie base, impossible de changer d'implémentation sans toucher au service. Version inversée :
class UserService {
constructor(repo) { // abstraction : "quelque chose avec creer/trouver"
this.repo = repo;
}
}
La décision de l'implémentation remonte à l'assemblage :
const service = new UserService(new PgUserRepository(pool)); // prod
const service = new UserService(new FakeUserRepository()); // tests
C'est EXACTEMENT votre db.js injecté dans le serveur — et le mécanisme central de NestJS : vous déclarerez ce qu'un service nécessite, le framework instanciera et branchera tout.
Les cinq principes en une table
| Principe | Une phrase | |
|---|---|---|
| S | Responsabilité unique | une raison de changer |
| O | Ouvert/fermé | étendre sans modifier |
| L | Liskov | le fils tient le contrat du père |
| I | Ségrégation d'interfaces | pas de dépendances inutiles |
| D | Inversion de dépendances | dépendre d'abstractions |
Exercice
- Trouvez la violation LSP :
class Carre extends Rectangle { setLargeur(w) { this.cote = w; this.hauteur = w; } }. - Réécrivez UserService pour respecter DIP, puis assemblez deux variantes.
- Pourquoi DIP rend-il les tests rapides ?
Résumé
- LSP : l'héritage est un contrat ; ISP : interfaces minces ; DIP : injections d'abstractions.
- DIP est LA porte d'entrée de NestJS : même motif, automatisé.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Question 1. Un code utilisant Rectangle suppose largeur et hauteur indépendantes ; Carré casse cette attente → substitut invalide. La modélisation correcte n'est pas « Carré EST-UN Rectangle » mais deux formes partageant une interface Aire().
Question 3. Le fake en mémoire répond en microsecondes, sans base ni nettoyage : on teste la logique métier isolée, des centaines de fois par minute.