Introduction : Pourquoi SOLID est important en TypeScript
SOLID est un acronyme de cinq principes fondamentaux de conception orientée objet, formulés par Robert Martin (Uncle Bob). Dans le contexte de TypeScript, ces principes acquièrent une valeur particulière grâce au système de typage strict et au support des paradigmes modernes de la POO. L'application de SOLID rend le code plus flexible, testable et maintenable, ce qui est crucial pour les projets évolutifs.
Dans cet article, nous analyserons chaque principe SOLID avec des exemples pratiques en TypeScript, montrerons les anti-patrons et leurs implémentations correctes. Vous apprendrez à écrire du code qui ne se casse pas lors de l'ajout de nouvelles fonctionnalités.
1. Single Responsibility Principle (SRP) — Principe de responsabilité unique
Théorie
Chaque classe doit avoir une seule raison de changer. Cela signifie que la classe ne doit être responsable que d'une seule partie de la fonctionnalité du programme. Si une classe effectue plusieurs tâches, elle est plus difficile à maintenir et à tester.
Anti-patron (violation du SRP)
class UserService { saveUser(user: User): void { // Sauvegarde en base de données console.log('User saved'); }
sendEmail(user: User, message: string): void { // Envoi d'email console.log(`Email sent to ${user.email}`); }
generateReport(user: User): string { // Génération de rapport return `Report for ${user.name}`; }}Implémentation correcte (SRP)
class UserRepository { save(user: User): void { // Uniquement la sauvegarde }}
class EmailService { send(user: User, message: string): void { // Uniquement l'envoi d'email }}
class ReportGenerator { generate(user: User): string { // Uniquement la génération de rapports }}2. Open-Closed Principle (OCP) — Principe d'ouverture/fermeture
Théorie
Les classes doivent être ouvertes à l'extension, mais fermées à la modification. Les nouvelles fonctionnalités doivent être ajoutées par héritage ou composition, et non en modifiant le code existant.
Anti-patron (violation de l'OCP)
class PaymentProcessor { processPayment(type: string, amount: number): void { if (type === 'card') { // Traitement par carte } else if (type === 'paypal') { // Traitement PayPal } else if (type === 'crypto') { // Traitement crypto-monnaie } // Lors de l'ajout d'un nouveau type — il faut modifier la classe ! }}Implémentation correcte (OCP)
interface PaymentMethod { process(amount: number): void;}
class CardPayment implements PaymentMethod { process(amount: number): void { console.log(`Processing card payment: $${amount}`); }}
class PayPalPayment implements PaymentMethod { process(amount: number): void { console.log(`Processing PayPal payment: $${amount}`); }}
class PaymentProcessor { constructor(private paymentMethod: PaymentMethod) {}
process(amount: number): void { this.paymentMethod.process(amount); }}3. Liskov Substitution Principle (LSP) — Principe de substitution de Barbara Liskov
Théorie
Les objets d'une classe de base doivent pouvoir être remplacés par des objets de ses sous-classes sans altérer le fonctionnement du programme. Les sous-classes ne doivent pas modifier le comportement attendu de la classe de base.
Anti-patron (violation du LSP)
class Rectangle { constructor(public width: number, public 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 {