Principes SOLID en JavaScript : théorie et pratique de A à Z
SOLID est l'acronyme de cinq principes fondamentaux de la programmation orientée objet, formulés par Robert Martin (Oncle Bob). Bien que JavaScript ne soit pas un langage POO classique (il utilise l'héritage prototypal), ces principes s'y appliquent également, en particulier dans le contexte de Node.js et des frameworks modernes. Le respect de SOLID rend le code plus flexible, testable et maintenable. Dans cet article, nous analyserons chaque principe avec des exemples pratiques en JavaScript.
1. S — Single Responsibility Principle (Principe de responsabilité unique)
Une classe ou un module ne doit avoir qu'une seule raison de changer. Autrement dit, chaque entité doit être responsable d'une tâche spécifique.
Mauvais exemple : La classe User gère à la fois les données utilisateur et envoie des notifications par email.
class User { constructor(name, email) { this.name = name; this.email = email; }
saveToDatabase() { // Logique de sauvegarde en BDD console.log(`Sauvegarde de l'utilisateur ${this.name} en BDD`); }
sendWelcomeEmail() { // Logique d'envoi d'email console.log(`Envoi d'un message de bienvenue à ${this.email}`); }}Bon exemple : Nous séparons les responsabilités en deux classes.
class User { constructor(name, email) { this.name = name; this.email = email; }}
class UserRepository { save(user) { console.log(`Sauvegarde de l'utilisateur ${user.name} en BDD`); }}
class EmailService { sendWelcome(user) { console.log(`Envoi d'un message de bienvenue à ${user.email}`); }}
// Utilisationconst user = new User('Ivan', 'ivan@example.com');const repo = new UserRepository();const emailService = new EmailService();
repo.save(user);emailService.sendWelcome(user);2. O — Open-Closed Principle (Principe d'ouverture/fermeture)
Les entités logicielles doivent être ouvertes à l'extension, mais fermées à la modification. Cela signifie que de nouvelles fonctionnalités doivent être ajoutées via du nouveau code, et non en modifiant le code existant.
Mauvais exemple : La classe AreaCalculator nécessite une modification lors de l'ajout d'une nouvelle forme.
class AreaCalculator { calculate(shape) { if (shape.type === 'circle') { return Math.PI * shape.radius ** 2; } else if (shape.type === 'rectangle') { return shape.width * shape.height; } // Lors de l'ajout d'une nouvelle forme, il faut modifier cette méthode }}Bon exemple : Nous utilisons le polymorphisme et l'héritage.
class Shape { area() { throw new Error('La méthode area() doit être redéfinie'); }}
class Circle extends Shape { constructor(radius) { super(); this.radius = radius; }
area() { return Math.PI * this.radius ** 2; }}
class Rectangle extends Shape { constructor(width, height) { super(); this.width = width; this.height = height; }
area() { return this.width * this.height; }}
class AreaCalculator { calculate(shape) { return shape.area(); }}
// Utilisationconst calculator = new AreaCalculator();const circle = new Circle(5);const rectangle = new Rectangle(4, 6);
console.log(calculator.calculate(circle)); // 78.54console.log(calculator.calculate(rectangle)); // 243. L — Liskov Substitution Principle (Principe de substitution de Barbara Liskov)
Les objets d'un programme doivent être remplaçables par des instances de leurs s