Einführung: Warum Ihr Code Dependency Injection braucht
Jeder JavaScript-Entwickler stößt früher oder später auf das Problem von "fest gekoppeltem" (tightly coupled) Code. Stellen Sie sich eine Klasse UserService vor, die innerhalb ihrer selbst eine Instanz von Database und Logger erstellt. Was passiert, wenn Sie UserService ohne eine echte Datenbank testen möchten? Oder den Logger durch eine andere Implementierung ersetzen wollen? Im besten Fall schreiben Sie die gesamte Klasse um, im schlimmsten Fall zerstören Sie die gesamte Anwendung.
Die Lösung für dieses Problem ist Dependency Injection (DI). Dies ist eines der wichtigsten Entwurfsprinzipien, das dem Konzept von Inversion of Control (IoC) zugrunde liegt. In diesem Artikel werden wir untersuchen, was DI ist, wie man es in reinem JavaScript (Node.js) ohne die Verwendung von Frameworks implementiert und warum es für die Erstellung skalierbarer und testbarer Anwendungen von entscheidender Bedeutung ist.
Was ist Dependency Injection in einfachen Worten?
Stellen Sie sich vor, Sie bauen ein Haus. Anstatt die Steckdosen für immer in die Wände einzumauern, installieren Sie standardisierte Anschlüsse. Heute schließen Sie eine alte Lampe an, morgen eine intelligente Leuchte. Sie ändern nicht die Wände – Sie ändern die Abhängigkeiten.
In der Programmierung ist Dependency Injection ein Muster, bei dem ein Objekt seine Abhängigkeiten (andere Objekte, Dienste, Konfigurationen) von außen erhält, anstatt sie selbst zu erstellen. Anstatt zu schreiben:
// ❌ Schlecht: feste Kopplungclass UserService { constructor() { this.db = new Database('mongodb://...'); this.logger = new ConsoleLogger(); }}Schreiben wir:
// ✅ Gut: Dependency Injectionclass UserService { constructor(database, logger) { this.db = database; this.logger = logger; }}Jetzt weiß UserService nicht, wie Database und Logger erstellt werden. Seine Aufgabe ist es nur, sie zu verwenden. Das ist Inversion of Control: Die Kontrolle über die Erstellung von Abhängigkeiten geht von der Klasse auf den aufrufenden Code über.
Arten der Dependency Injection
Es gibt drei Hauptarten der Injektion:
- Über den Konstruktor (Constructor Injection) – die gebräuchlichste und bevorzugte Methode. Abhängigkeiten werden bei der Erstellung des Objekts übergeben.
- Über Eigenschaften (Property Injection) – Abhängigkeiten werden nach der Erstellung des Objekts gesetzt. Praktisch für optionale Abhängigkeiten.
- Über Methoden (Method Injection) – die Abhängigkeit wird einer bestimmten Methode übergeben, wenn sie nur für eine einzelne Aktion benötigt wird.
Einen einfachen DI-Container in Node.js schreiben
In großen Projekten wird die manuelle Verwaltung von Abhängigkeiten komplex. Hier kommt der DI-Container ins Spiel – ein Objekt, das weiß, wie alle Abhängigkeiten Ihrer Anwendung erstellt und gespeichert werden. Lassen Sie uns einen einfachen, aber funktionsfähigen Container in reinem JavaScript schreiben.
Unser Container wird zwei Modi unterstützen: singleton (eine Instanz für die gesamte Anwendung) und transient (eine neue Instanz bei jeder Anfrage).
// di-container.jsclass DIContainer { constructor() { this.registry = new Map(); this.instances = new Map(); }
// Registriert eine Abhängigkeit mit einer Factory-Funktion register(name, factory, { singleton = true } = {}) { this.registry.set(name, { factory, singleton }); if (singleton) { this.instances.delete(name); // Setzt den Cache bei erneuter Registrierung zurück } }
// Holt eine Instanz der Abhängigkeit resolve(name) { const item = this.registry.get(name); if (!item) { throw new Error(`Abhängigkeit "${name}" nicht gefunden`); }}