Introduction : Pourquoi SOLID est important pour le développeur TypeScript
SOLID — ce sont cinq principes fondamentaux de la programmation orientée objet, formulés par Robert Martin (Oncle Bob). Dans le contexte de TypeScript, qui combine le typage statique et la flexibilité de JavaScript, l'application des principes SOLID permet de créer des applications évolutives, testables et facilement maintenables. Dans cet article, nous analyserons chaque principe avec une base théorique et des exemples pratiques en TypeScript.
1. Single Responsibility Principle (SRP) — Principe de responsabilité unique
Théorie
Une classe ne doit avoir qu'une seule raison de changer. Cela signifie que chaque module, classe ou fonction est responsable d'exactement une fonctionnalité.
Pratique en TypeScript
Considérons une violation du SRP avec l'exemple d'une classe qui gère les utilisateurs et l'envoi de notifications :
// ❌ Violation du SRPclass UserManager { constructor(private name: string, private email: string) {}
saveUser(): void { console.log(`Utilisateur ${this.name} sauvegardé en BD`); }
sendWelcomeEmail(): void { console.log(`Email de bienvenue envoyé à ${this.email}`); }}Version corrigée avec séparation des responsabilités :
// ✅ Respect du SRPclass User { constructor(public name: string, public email: string) {}}
class UserRepository { save(user: User): void { console.log(`Utilisateur ${user.name} sauvegardé en BD`); }}
class EmailService { sendWelcomeEmail(user: User): void { console.log(`Email de bienvenue envoyé à ${user.email}`); }}2. Open/Closed Principle (OCP) — Principe d'ouverture/fermeture
Théorie
Les entités logicielles doivent être ouvertes à l'extension, mais fermées à la modification. En d'autres termes, vous devez pouvoir ajouter de nouvelles fonctionnalités sans modifier le code existant.
Pratique en TypeScript
Exemple avec le calcul de réductions pour différents types de clients :
// ❌ Violation du OCPclass DiscountCalculator { calculateDiscount(customerType: string, amount: number): number { if (customerType === 'regular') { return amount * 0.1; } else if (customerType === 'premium') { return amount * 0.2; } else if (customerType === 'vip') { return amount * 0.3; } return 0; }}// ✅ Respect du OCP via polymorphismeinterface DiscountStrategy { calculate(amount: number): number;}
class RegularDiscount implements DiscountStrategy { calculate(amount: number): number { return amount * 0.1; }}
class PremiumDiscount implements DiscountStrategy { calculate(amount: number): number { return amount * 0.2; }}
class DiscountCalculator { constructor(private strategy: DiscountStrategy) {}
calculate(amount: number): number { return this.strategy.calculate(amount); }}3. Liskov Substitution Principle (LSP) — Principe de substitution de Barbara Liskov
Théorie
Les objets d'un programme doivent être remplaçables par des instances de leurs sous-types sans altérer la correction du programme. En termes simples : si vous avez une classe de base, tout héritier doit fonctionner correctement partout où la classe de base est utilisée.
Pratique en TypeScript
Exemple classique avec les figures géométriques :
// ❌ Violation du LSPclass Rectangle { constructor(protected width: number, protected height: number) {}
setWidth(width: number): void { this.width = width; }
setHeight(height: number): void { this.height = height; }
getArea(): number { return this.width * this.height; }}
class Square extends Rectangle { setWidth(width: number): void { this.width = width; this.height = width; // Violation ! }
setHeight(height: number): vo