Was ist Dependency Injection in TypeScript und warum wird es benötigt?
Dependency Injection (DI) ist ein Entwurfsmuster, das das Prinzip der Inversion of Control (IoC) implementiert. Anstatt dass ein Objekt seine Abhängigkeiten selbst erstellt, werden diese von außen "injiziert". In TypeScript ist dieses Muster dank der strengen Typisierung und der Möglichkeiten von Dekoratoren besonders nützlich.
Stellen Sie sich vor, Sie bauen eine Anwendung für einen Online-Shop. Sie haben eine Klasse OrderService, die Benachrichtigungen senden soll. Ohne DI wäre sie fest an eine bestimmte Versandart (z. B. E-Mail) gebunden. Mit DI können Sie E-Mail einfach gegen SMS oder Push-Benachrichtigungen austauschen, ohne den Code von OrderService zu ändern.
Warum sollte man DI in TypeScript verwenden?
- Testbarkeit: Reale Dienste können in Unit-Tests leicht durch Mock-Objekte ersetzt werden.
- Flexibilität: Das System wird modular, Abhängigkeiten können ohne Änderung des Client-Codes ausgetauscht werden.
- Vereinfachte Wartung: Jede Klasse ist nur für ihre eigene Geschäftslogik verantwortlich, nicht für die Erstellung anderer Objekte.
- Typsicherheit: TypeScript überprüft Typen zur Kompilierzeit, was die Anzahl der Laufzeitfehler reduziert.
Basisimplementierung von DI in TypeScript ohne Frameworks
Beginnen wir mit einem einfachen Beispiel. Wir erstellen ein Interface für einen Benachrichtigungsdienst und zwei Implementierungen davon.
// Interface definiereninterface INotificationService { send(message: string): void;}
// Implementierung für E-Mailclass EmailNotificationService implements INotificationService { send(message: string): void { console.log(`E-Mail senden: ${message}`); }}
// Implementierung für SMSclass SMSNotificationService implements INotificationService { send(message: string): void { console.log(`SMS senden: ${message}`); }}
// Klasse, die DI verwendetclass OrderService { constructor(private notificationService: INotificationService) {}
processOrder(orderId: number): void { // Logik zur Auftragsverarbeitung this.notificationService.send(`Auftrag #${orderId} verarbeitet`); }}
// Verwendungconst emailService = new EmailNotificationService();const orderService = new OrderService(emailService);orderService.processOrder(123); // E-Mail senden: Auftrag #123 verarbeitetIn diesem Beispiel weiß OrderService nicht, welcher Benachrichtigungsdienst genau verwendet wird. Er ruft einfach die Methode send über das Interface auf.
Fortgeschrittenes DI: Erstellen eines einfachen IoC-Containers
Für große Projekte wird die manuelle Abhängigkeitsinjektion umständlich. Hier kommt ein IoC-Container zur Hilfe. Betrachten wir, wie man ihn mit TypeScript-Dekoratoren implementiert.
Schritt 1: Aktivieren Sie die Unterstützung für Dekoratoren in tsconfig.json
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true }}Schritt 2: Erstellen Sie einen DI-Container
// di-container.tstype Constructor<T> = new (...args: any[]) => T;
class DIContainer { private services = new Map<string, any>();
register<T>(token: string, implementation: Constructor<T>): void { this.services.set(token, implementation); }
resolve<T>(token: string): T { const Implementation = this.services.get(token); if (!Implementation) { throw new Error(`Dienst "${token}" ist nicht registriert`); }
// Parametertypen des Konstruktors über Reflexion abrufen const paramTypes: Constructor<any>[] = Reflect.getMetadata('design:paramtypes', Implementation) || [];
// Abhängigkeiten rekursiv auflösen const dependencies = param