Principes SOLID en JavaScript : théorie et pratique de A à Z
\n\nLe développement d’un logiciel de qualité nécessite non seulement la connaissance de la syntaxe du langage, mais aussi la compréhension des principes d’architecture. SOLID est un ensemble de cinq principes fondamentaux de conception orientée objet qui aident à créer un code flexible, maintenable et évolutif. Dans cet article, nous analyserons chaque principe SOLID dans le contexte de JavaScript (Node.js) avec des exemples pratiques.
\n\nQu’est-ce que SOLID ?
\n\nL’acronyme SOLID a été introduit par Robert Martin (Oncle Bob) et comprend les principes suivants :
\n\n- \n
- S — Single Responsibility Principle (Principe de responsabilité unique) \n
- O — Open/Closed Principle (Principe d’ouverture/fermeture) \n
- L — Liskov Substitution Principle (Principe de substitution de Barbara Liskov) \n
- I — Interface Segregation Principle (Principe de ségrégation des interfaces) \n
- D — Dependency Inversion Principle (Principe d’inversion des dépendances) \n
Ces principes sont universels et applicables à tout langage de programmation, y compris JavaScript. Examinons chacun d’eux en détail.
\n\n1. Single Responsibility Principle (SRP)
\n\nDéfinition : Une classe ou un module ne doit avoir qu’une seule raison de changer. En d’autres termes, chaque module est responsable d’une tâche spécifique.
\n\nMauvais exemple : Une classe UserService qui est responsable à la fois de la sauvegarde de l’utilisateur, de l’envoi d’email et de la génération de rapports.
// ❌ Violation du SRP\nclass UserService {\n saveUser(user) {\n // Sauvegarde dans la base de données\n console.log('User saved');\n }\n\n sendEmail(user) {\n // Envoi d’email\n console.log('Email sent to ' + user.email);\n }\n\n generateReport(user) {\n // Génération de rapport\n console.log('Report generated for ' + user.name);\n }\n}\n\nBon exemple : Nous séparons les responsabilités en classes distinctes.
\n\n// ✅ Respect du SRP\nclass UserRepository {\n save(user) {\n console.log('User saved');\n }\n}\n\nclass EmailService {\n send(user) {\n console.log('Email sent to ' + user.email);\n }\n}\n\nclass ReportGenerator {\n generate(user) {\n console.log('Report generated for ' + user.name);\n }\n}\n\n// Utilisation\nconst user = { name: 'Иван', email: 'ivan@example.com' };\nconst userRepo = new UserRepository();\nconst emailService = new EmailService();\nconst reportGen = new ReportGenerator();\n\nuserRepo.save(user);\nemailService.send(user);\nreportGen.generate(user);\n\nMaintenant, chaque classe a une seule responsabilité, et les modifications dans un module n’affectent pas les autres.
\n\n2. Open/Closed Principle (OCP)
\n\nDéfinition : Les entités logicielles (classes, modules, fonctions) doivent être ouvertes à l’extension, mais fermées à la modification.
\n\nMauvais exemple : Une fonction qui calcule l’aire des formes utilise des instructions conditionnelles.
\n\n// ❌ Violation du OCP\nfunction calculateArea(shape) {\n if (shape.type === 'circle') {\n return Math.PI * shape.radius ** 2;\n } else if (shape.type === 'rectangle') {\n return shape.width * shape.height;\n }\n // Lors de l’ajout d’une nouvelle forme, la fonction doit être modifiée\n return 0;\n}\n\nBon exemple : Nous utilisons le polymorphisme et l’héritage.
\n\n// ✅ Respect du OCP\nclass Shape {\n area() {\n throw new Error('La méthode area() doit être implémentée');\n }\n}\n\nclass Circle extends Shape {\n constructor(radius) {\n super();\n this.radius = radius;\n }\n\n area() {\n return Math.PI * this.radius ** 2;\n }\n}\n\nclass Rectangle extends Shape {\n constructor