Principes SOLID en JavaScript : de la théorie à la pratique sur Node.js
SOLID est un ensemble de cinq principes fondamentaux de conception orientée objet formulés par Robert Martin (Uncle Bob). Ces principes aident à créer un code flexible, maintenable et évolutif. Bien que beaucoup associent SOLID aux langages POO classiques (Java, C#), ils sont parfaitement applicables en JavaScript, surtout lors du développement sur Node.js. Dans cet article, nous allons décomposer chaque principe avec des exemples concrets.
Principe de responsabilité unique (SRP)
Le SRP stipule : "Une classe (ou un module) ne devrait avoir qu'une seule raison de changer". Cela signifie que chaque module est responsable d'une tâche spécifique.
Mauvais exemple (violation du SRP) :
class UserService { createUser(data) { /* logique de création d'utilisateur */ } sendWelcomeEmail(email) { /* envoi d'email */ } logActivity(action) { /* journalisation */ }}Ici, une seule classe est responsable de la gestion des utilisateurs, de l'envoi d'emails et de la journalisation. Si le format du journal change, vous devriez modifier UserService — même si sa tâche principale n'a pas changé.
Bon exemple (respect du SRP) :
class UserRepository { createUser(data) { /* seulement le travail avec la base de données */ }}
class EmailService { sendWelcomeEmail(email) { /* seulement l'envoi */ }}
class Logger { logActivity(action) { /* seulement la journalisation */ }}
class UserService { constructor(userRepo, emailService, logger) { this.userRepo = userRepo; this.emailService = emailService; this.logger = logger; } createUser(data) { const user = this.userRepo.createUser(data); this.emailService.sendWelcomeEmail(user.email); this.logger.logActivity('Utilisateur créé'); return user; }}Maintenant, chaque classe est responsable de son propre domaine de responsabilité. En les combinant dans UserService, nous gagnons en flexibilité et en isolation des changements.
Principe d'ouverture/fermeture (OCP)
L'OCP stipule : "Les entités logicielles doivent être ouvertes à l'extension, mais fermées à la modification". C'est-à-dire que nous devrions pouvoir ajouter de nouvelles fonctionnalités sans modifier le code existant.
Mauvais exemple (violation de l'OCP) :
class PaymentProcessor { processPayment(type, amount) { if (type === 'card') { /* traitement par carte */ } else if (type === 'paypal') { /* traitement PayPal */ } // Lors de l'ajout d'un nouveau type — vous devez modifier cette méthode }}Bon exemple (respect de l'OCP) :
class PaymentStrategy { pay(amount) { throw new Error('Doit être implémenté'); }}
class CardPayment extends PaymentStrategy { pay(amount) { console.log(`Payé ${amount} par Carte`); }}
class PayPalPayment extends PaymentStrategy { pay(amount) { console.log(`Payé ${amount} par PayPal`); }}
class PaymentProcessor { constructor(strategy) { this.strategy = strategy; } processPayment(amount) { this.strategy.pay(amount); }}
// Utilisationconst cardPayment = new PaymentProcessor(new CardPayment());cardPayment.processPayment(100);
const paypalPayment = new PaymentProcessor(new PayPalPayment());paypalPayment.processPayment(200);Maintenant, pour ajouter un nouveau moyen de paiement, il suffit de créer une nouvelle classe héritant de PaymentStrategy et de la passer à PaymentProcessor. Le code original ne change pas.
Principe de substitution de Liskov (LSP)
Le LSP stipule : "Les objets d'un programme doivent être remplaçables par des instances de leurs sous-types sans altérer la correction du programme". En d'autres termes, si vous avez une classe de base