Einleitung: Was ist Dependency Injection und warum wird sie benötigt?
\n\nStellen Sie sich vor, Sie bauen einen Computer zusammen. Anstatt einen fertigen All-in-One-Computer zu kaufen, wählen Sie separat Prozessor, Grafikkarte, Arbeitsspeicher aus und schließen diese an das Mainboard an. Wenn Sie die Grafikkarte aufrüsten möchten, müssen Sie nicht den ganzen Computer austauschen – Sie ersetzen einfach eine Komponente. Dependency Injection (DI) in der Programmierung funktioniert nach dem gleichen Prinzip.
\n\nDependency Injection (Abhängigkeitsinjektion) ist ein Entwurfsmuster, bei dem ein Objekt seine Abhängigkeiten von außen erhält, anstatt sie selbst zu erstellen. Im Kontext von JavaScript und Node.js bedeutet dies, dass eine Klasse oder Funktion keine Instanzen anderer Klassen in sich selbst erstellt, sondern diese über den Konstruktor oder Parameter entgegennimmt.
\n\nDieser Ansatz ist Teil des Prinzips der Abhängigkeitsinversion (Dependency Inversion Principle) aus SOLID und macht den Code flexibler, testbarer und wartbarer. In diesem Artikel werden wir DI anhand praktischer Beispiele für Anfänger-Entwickler erläutern.
\n\nDas Problem: Feste Kopplung von Code
\n\nBetrachten wir eine typische Situation in einer Node.js-Anwendung. Angenommen, wir haben einen Dienst zum Senden von E-Mail-Benachrichtigungen:
\n\n// Ohne Dependency Injection\nclass EmailService {\n sendEmail(to, subject, body) {\n console.log(`Sende E-Mail an ${to}: ${subject}`);\n // eigentliche Sende-Logik\n }\n}\n\nclass UserService {\n constructor() {\n this.emailService = new EmailService(); // ❌ Feste Kopplung\n }\n\n registerUser(email) {\n // Benutzerregistrierungslogik\n this.emailService.sendEmail(email, 'Willkommen!', '...');\n }\n}\n\nconst userService = new UserService();\nuserService.registerUser('user@example.com');\n\n\nWas ist das Problem? Die Klasse UserService ist fest an eine bestimmte Implementierung von EmailService gebunden. Wenn Sie:
- \n
- Das Senden von E-Mails durch das Senden von SMS ersetzen möchten \n
- Eine Protokollierung aller gesendeten Nachrichten hinzufügen möchten \n
UserServiceohne tatsächliches Senden von E-Mails testen möchten \n
müssen Sie den Code von UserService umschreiben. Dies verletzt das Open/Closed-Prinzip.
Die Lösung: Abhängigkeitsinjektion über den Konstruktor
\n\nDer erste und einfachste Weg der Abhängigkeitsinjektion ist die Übergabe über den Konstruktor der Klasse. Lassen Sie uns unser Beispiel umschreiben:
\n\n// Mit Dependency Injection\nclass EmailService {\n sendEmail(to, subject, body) {\n console.log(`Sende E-Mail an ${to}: ${subject}`);\n }\n}\n\nclass SmsService {\n sendSms(phone, message) {\n console.log(`Sende SMS an ${phone}: ${message}`);\n }\n}\n\nclass UserService {\n constructor(notificationService) {\n this.notificationService = notificationService; // ✅ Abhängigkeitsinjektion\n }\n\n registerUser(contact, isPhone = false) {\n // Registrierungslogik\n if (isPhone) {\n this.notificationService.sendSms(contact, 'Willkommen!');\n } else {\n this.notificationService.sendEmail(contact, 'Willkommen!', '...');\n }\n }\n}\n\n// Verwendung\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\nJetzt weiß UserService nicht, welcher Dienst genau verwendet wird – E-Mail oder SMS. Er ruft einfach die Methode sendEmail oder sendSms des übergebenen Objekts auf. Dies wird als Abhängigkeitsinversion bezeichnet: Das High-Level-Modul hängt nicht von einem Low-Level-Modul ab, beide hängen von einer Abstraktion ab.
Vorteile dieses Ansatzes:
\n- \n
- Flexibilität: einfacher Austausch