Introduction : Qu'est-ce que l'injection de dépendances et pourquoi en a-t-on besoin ?
\n\nImaginez que vous assemblez un ordinateur. Au lieu d'acheter un monobloc prêt à l'emploi, vous choisissez séparément un processeur, une carte graphique, de la mémoire vive et vous les connectez à la carte mère. Si vous souhaitez améliorer la carte graphique, vous n'avez pas à changer tout l'ordinateur — vous remplacez simplement un composant. L'injection de dépendances (DI) en programmation fonctionne sur le même principe.
\n\nL'injection de dépendances (Dependency Injection) est un patron de conception dans lequel un objet reçoit ses dépendances de l'extérieur, plutôt que de les créer lui-même. Dans le contexte de JavaScript et Node.js, cela signifie qu'une classe ou une fonction ne crée pas d'instances d'autres classes en son sein, mais les reçoit via le constructeur ou les paramètres.
\n\nCette approche fait partie du principe d'Inversion de dépendances (Dependency Inversion Principle) de SOLID et rend le code plus flexible, testable et maintenable. Dans cet article, nous allons décortiquer la DI avec des exemples pratiques pour les développeurs débutants.
\n\nProblème : couplage fort du code
\n\nConsidérons une situation typique dans une application Node.js. Supposons que nous ayons un service pour envoyer des notifications par email :
\n\n// Sans injection de dépendances\nclass EmailService {\n sendEmail(to, subject, body) {\n console.log(`Envoi d'email à ${to}: ${subject}`);\n // logique réelle d'envoi\n }\n}\n\nclass UserService {\n constructor() {\n this.emailService = new EmailService(); // ❌ Couplage fort\n }\n\n registerUser(email) {\n // Logique d'inscription utilisateur\n this.emailService.sendEmail(email, 'Bienvenue !', '...');\n }\n}\n\nconst userService = new UserService();\nuserService.registerUser('user@example.com');\n\n\nQuel est le problème ? La classe UserService est fortement liée à une implémentation spécifique de EmailService. Si vous souhaitez :
- \n
- Remplacer l'envoi d'email par l'envoi de SMS \n
- Ajouter la journalisation de tous les messages envoyés \n
- Tester
UserServicesans envoyer réellement de courriels \n
Vous devrez réécrire le code de UserService. Cela viole le principe d'ouverture/fermeture (Open/Closed Principle).
Solution : injection de dépendances via le constructeur
\n\nLa première et la plus simple façon d'injecter des dépendances est de les transmettre via le constructeur de la classe. Réécrivons notre exemple :
\n\n// Avec injection de dépendances\nclass EmailService {\n sendEmail(to, subject, body) {\n console.log(`Envoi d'email à ${to}: ${subject}`);\n }\n}\n\nclass SmsService {\n sendSms(phone, message) {\n console.log(`Envoi de SMS à ${phone}: ${message}`);\n }\n}\n\nclass UserService {\n constructor(notificationService) {\n this.notificationService = notificationService; // ✅ Injection de dépendance\n }\n\n registerUser(contact, isPhone = false) {\n // Logique d'inscription\n if (isPhone) {\n this.notificationService.sendSms(contact, 'Bienvenue !');\n } else {\n this.notificationService.sendEmail(contact, 'Bienvenue !', '...');\n }\n }\n}\n\n// Utilisation\nconst emailService = new EmailService();\nconst userServiceWithEmail = new UserService(emailService);\nuserServiceWithEmail.registerUser('user@example.com');\n\nconst smsService = new SmsService();\nconst userServiceWithSms = new UserService(smsService);\nuserServiceWithSms.registerUser('+1234567890', true);\n\n\nMaintenant, UserService ne sait pas quel service est utilisé — email ou SMS. Il appelle simplement la méthode sendEmail ou sendSms de l'objet passé. C'est ce qu'on appelle l'inversion de dépendances : le module de haut niveau ne dépend pas du module de bas niveau, les deux dépendent d'une abstraction.
Avantages de cette approche :
\n- \n
- Flexibilité : facile à remplacer