In diesem Artikel erklären wir, wie du Dependency Injection in TypeScript mit der InversifyJS-Bibliothek umsetzt. Ziel ist es, die Vorteile von Dependency Injection zu erläutern und zu zeigen, wie du deinen Code durch den Einsatz von InversifyJS effizienter und wartungsfreundlicher gestalten kannst.
Was ist Dependency Injection in TypeScript?
Dependency Injection (DI) ist ein Entwurfsmuster, das die Kopplung zwischen Klassen reduziert (Loose Coupling). Es ermöglicht, dass Klassen über Schnittstellen oder Abstraktionen kommunizieren, anstatt direkt miteinander zu arbeiten. DI erhöht die Testbarkeit und Flexibilität des Codes. In TypeScript gibt es mit InversifyJS eine leistungsstarke Bibliothek, die diese Implementierung vereinfacht.
Warum Dependency Injection?
Ohne Dependency Injection sind Klassen oft fest an konkrete Implementierungen gebunden. Dies erschwert spätere Änderungen und erhöht die Fehleranfälligkeit. DI löst dieses Problem, indem es die Abhängigkeiten zentral verwaltet und automatisch bereitstellt.
Dependency Injection in TypeScript mit einem Wettervorhersage-System
Als Beispiel entwickeln wir ein kleines Wettervorhersage-System mit zwei verschiedenen Vorhersagern: "WeatherSatellite" und " WeatherStation". Beide Vorhersager implementieren die Wettervorhersage, jedoch wollen wir das System so gestalten, dass die konkrete Vorhersagemethode flexibel bleibt.
Beispiel ohne Dependency Injection
class WeatherSatellite {
getTemperature(): number {
return 20;
}
}
class WeatherStation {
getTemperature(): number {
return 22;
}
}
class Person {
private weatherSatellite: WeatherSatellite;
constructor() {
this.weatherSatellite = new WeatherSatellite();
}
}
UML-Diagramm: Aktuelles Design ohne Dependency Injection
Problem: Die Person
-Klasse entscheidet, welche Wettervorhersagemethode verwendet wird. Das führt zu einer starken Kopplung und
erschwert es, die Implementierung zu ändern oder zu erweitern.
Verbesserung: Einführung von Interfaces
In TypeScript können wir Interfaces nutzen, um Abhängigkeiten zu abstrahieren. Hier definieren wir ein IWeatherForecasting
-Interface:
interface IWeatherForecasting {
getTemperature(): number;
}
class WeatherSatellite implements IWeatherForecasting {
getTemperature(): number {
return 20;
}
}
class WeatherStation implements IWeatherForecasting {
getTemperature(): number {
return 22;
}
}
class Person {
private weatherForecasting: IWeatherForecasting;
constructor(weatherForecasting: IWeatherForecasting) {
this.weatherForecasting = weatherForecasting;
}
}
UML-Diagramm: Interface Implementierung ohne Dependency Injection
Problem: Obwohl jetzt ein Interface verwendet wird, entscheidet die Person
-Klasse immer noch, welche Implementierung genutzt wird. Das
widerspricht dem DI-Prinzip.
Lösung: Dependency Injection mit InversifyJS
Um diese Abhängigkeit vollständig zu lösen, verwenden wir InversifyJS, um die benötigten Objekte automatisch zu injizieren.
Code-Beispiel: Verwendung von InversifyJS für Dependency Injection
Zuerst müssen wir InversifyJS und reflect-metadata installieren:
npm install inversify reflect-metadata
Erstelle dann den TypeScript-Code mit InversifyJS:
import 'reflect-metadata';
import {Container, injectable, inject} from 'inversify';
const TYPES = {
IWeatherForecasting: Symbol.for('IWeatherForecasting')
};
interface IWeatherForecasting {
getTemperature(): number;
}
@injectable()
class WeatherSatellite implements IWeatherForecasting {
getTemperature(): number {
return 20;
}
}
@injectable()
class WeatherStation implements IWeatherForecasting {
getTemperature(): number {
return 22;
}
}
@injectable()
class Person {
private weatherForecasting: IWeatherForecasting;
constructor(@inject(TYPES.IWeatherForecasting) weatherForecasting: IWeatherForecasting) {
this.weatherForecasting = weatherForecasting;
}
getForecast(): number {
return this.weatherForecasting.getTemperature();
}
}
const container = new Container();
container.bind<IWeatherForecasting>(TYPES.IWeatherForecasting).to(WeatherSatellite);
const person = container.resolve(Person);
console.log(person.getForecast()); // Ausgabe: 20
Erklärung
- @injectable: Dekorator, der die Klasse für Dependency Injection verfügbar macht.
- @inject: Ermöglicht die Injektion der Abhängigkeiten in den Konstruktor der Klasse.
- Container: Definiert, welche Implementierungen für die jeweiligen Abhängigkeiten verwendet werden.
UML-Diagramm: Design mit Dependency Injection
Vorteil
Die Person
-Klasse ist nun vollständig unabhängig von der Implementierung der Wettervorhersage. Änderungen an der Implementierung können
einfach im DI-Container vorgenommen werden.
Erweiterung des DI-Containers
Es ist auch möglich, andere Implementierungen oder Abhängigkeiten zum Container hinzuzufügen, indem man verschiedene Bindungen mit .to()
verwendet.
Häufig gestellte Fragen zur Dependency Injection
Warum sollte ich InversifyJS statt manueller Dependency Injection verwenden?
InversifyJS vereinfacht die Verwaltung von Abhängigkeiten und macht den Code modularer. Manuelles Verwalten von Abhängigkeiten kann zu Fehlern und schwer wartbarem Code führen, insbesondere in größeren Anwendungen.
Kann InversifyJS auch in Frontend-Frameworks wie Angular verwendet werden?
Ja, InversifyJS kann sowohl im Backend (Node.js) als auch im Frontend mit Frameworks wie Angular oder React verwendet werden. Es ist jedoch wichtig zu prüfen, ob der gewählte DI-Ansatz gut in das jeweilige Framework integriert werden kann.
Fazit
Dependency Injection in TypeScript mit der Bibliothek InversifyJS hilft dabei, den Code modularer, flexibler und wartungsfreundlicher zu gestalten. InversifyJS ermöglicht eine einfache Verwaltung der Abhängigkeiten, wodurch sich der Code leichter anpassen und erweitern lässt.