Introduction : SOLID dans l'écosystème JavaScript
\nSOLID est un acronyme désignant cinq principes fondamentaux de conception orientée objet, formulés par Robert Martin. Dans le contexte de JavaScript, en particulier lors du développement sur Node.js avec TypeScript et des frameworks modernes (NestJS, Express), ces principes deviennent essentiels pour construire des applications évolutives et maintenables. À un niveau avancé, SOLID dépasse le cadre des simples classes et interfaces, touchant l'architecture des modules, l'injection de dépendances et les tests.
\nDans cet article, nous examinerons chaque principe SOLID avec des exemples de code pour Node.js, en mettant l'accent sur les nuances pratiques et les erreurs typiques. Vous apprendrez comment appliquer SOLID dans des projets réels, en utilisant les fonctionnalités de JavaScript (ES6+) et TypeScript.
\n\n1. Principe de responsabilité unique (SRP) en JavaScript
\nThéorie : Une classe ou un module ne doit avoir qu'une seule raison de changer. En JavaScript, cela est souvent violé lorsque la logique métier, le travail avec la base de données et le formatage des réponses sont mélangés dans un même fichier.
\nPratique (Node.js) : Séparez les responsabilités au niveau des services et des repositories.
\n// ❌ Violation du SRP : la classe gère les requêtes, valide et sauvegarde\nclass UserService {\n createUser(data) {\n if (!data.email) throw new Error('Email requis');\n const db = new Database();\n return db.save('users', data);\n }\n}\n\n// ✅ Respect du SRP : séparation en validateur, repository et service\nclass UserValidator {\n validate(data) {\n if (!data.email) throw new Error('Email requis');\n }\n}\n\nclass UserRepository {\n constructor(db) {\n this.db = db;\n }\n save(data) {\n return this.db.save('users', data);\n }\n}\n\nclass UserService {\n constructor(validator, repository) {\n this.validator = validator;\n this.repository = repository;\n }\n createUser(data) {\n this.validator.validate(data);\n return this.repository.save(data);\n }\n}\nUtilisez l'injection de dépendances (DI) pour passer le validateur et le repository au service. Cela simplifie les tests et le remplacement des composants.
\n\n2. Principe d'ouverture/fermeture (OCP) via le polymorphisme
\nThéorie : Les classes doivent être ouvertes à l'extension, mais fermées à la modification. En JavaScript, cela est réalisé via l'héritage, la composition ou les stratégies.
\nPratique (Node.js avec TypeScript) : Utilisez des classes abstraites ou des interfaces pour définir un contrat.
\n// Classe abstraite pour les rapports\nabstract class ReportGenerator {\n abstract generate(data: any): string;\n}\n\nclass PDFReport extends ReportGenerator {\n generate(data: any): string {\n return `PDF : ${JSON.stringify(data)}`;\n }\n}\n\nclass CSVReport extends ReportGenerator {\n generate(data: any): string {\n return `CSV : ${data.join(',')}`;\n }\n}\n\n// Le code client ne change pas lors de l'ajout de nouveaux formats\nclass ReportService {\n constructor(private generator: ReportGenerator) {}\n createReport(data: any) {\n return this.generator.generate(data);\n }\n}\nPour du JavaScript pur, utilisez des fabriques ou des stratégies (patron Strategy) pour éviter de modifier le code existant.
\n\n3. Principe de substitution de Liskov (LSP) dans le code asynchrone
\nThéorie : Les objets d'une classe de base doivent pouvoir être remplacés par des objets d'une classe dérivée sans altérer le fonctionnement du programme. En JavaScript, le LSP est souvent violé lors de la redéfinition de méthodes avec des contrats différents (par exemple, types de retour ou exceptions).
\nPratique (Node.js avec async/await) : Assurez-vous que les sous-classes n'affaiblissent pas les préconditions et ne renforcent pas les postconditions.
\nclass Database {\n async connect() { return 'con