Dependency Injection in TypeScript: für Anfänger
\n\nIn der Welt der Softwareentwicklung begegnet man häufig dem Begriff Dependency Injection (DI) oder Abhängigkeitsinjektion. Für einen Anfänger-Entwickler in TypeScript mag das einschüchternd klingen, aber in Wirklichkeit ist es ein einfaches und mächtiges Entwurfsmuster, das Ihren Code sauberer, testbarer und wartungsfreundlicher macht. In diesem Artikel werden wir erklären, was DI ist, warum es benötigt wird und wie man es in TypeScript in der Praxis anwendet.
\n\nWas ist Dependency Injection?
\n\nStellen Sie sich vor, Sie bauen einen Computer zusammen. Anstatt den Prozessor direkt im Werk auf das Mainboard zu löten, lassen Sie einen Steckplatz frei, in den Sie jeden kompatiblen Prozessor einsetzen können. Dependency Injection funktioniert nach dem gleichen Prinzip: Anstatt dass eine Klasse ihre Abhängigkeiten (z. B. eine Datenbank oder einen HTTP-Client) selbst erstellt, erhält sie diese von außen.
\n\nEinfach ausgedrückt: DI bedeutet, dass ein Objekt seine "Helfer" nicht in sich selbst erstellt, sondern sie fertig über den Konstruktor oder eine Methode erhält.
\n\nProblem ohne DI: Feste Kopplung
\n\nBetrachten wir ein Beispiel, das häufig in echten Projekten vorkommt:
\n\nclass UserService {\n private database: MySQLDatabase;\n\n constructor() {\n this.database = new MySQLDatabase(); // Feste Kopplung!\n }\n\n getUser(id: number) {\n return this.database.findUserById(id);\n }\n}\n\nHier ist UserService fest an MySQLDatabase gebunden. Wenn Sie auf PostgreSQL umsteigen oder Unit-Tests mit einer gefälschten Datenbank schreiben möchten, müssten Sie die gesamte Klasse umschreiben. Dies verstößt gegen das Open-Closed-Prinzip aus SOLID.
Lösung mit DI: Flexibilität und Testbarkeit
\n\nSchreiben wir nun dieselbe Klasse mit DI um:
\n\ninterface Database {\n findUserById(id: number): User | null;\n}\n\nclass UserService {\n constructor(private database: Database) {} // Abhängigkeit wird von außen übergeben\n\n getUser(id: number) {\n return this.database.findUserById(id);\n }\n}\n\n// Verwendung\nconst mySQL = new MySQLDatabase();\nconst service = new UserService(mySQL); // Abhängigkeit injizieren\n\nJetzt weiß UserService nicht, mit welcher Datenbank er arbeitet. Sie können problemlos MySQLDatabase durch PostgreSQLDatabase oder MockDatabase für Tests ersetzen.
Grundlegende Methoden der Abhängigkeitsinjektion
\n\nIn TypeScript gibt es drei grundlegende Methoden der Abhängigkeitsinjektion. Betrachten wir jede davon.
\n\n1. Konstruktorinjektion (Constructor Injection)
\n\nDies ist die gebräuchlichste und empfohlene Methode. Abhängigkeiten werden über den Konstruktor der Klasse übergeben.
\n\nclass Logger {\n log(message: string) {\n console.log(`[LOG]: ${message}`);\n }\n}\n\nclass OrderService {\n constructor(private logger: Logger) {}\n\n createOrder(data: any) {\n this.logger.log('Order created');\n // Logik zur Auftragserstellung\n }\n}\n\nconst logger = new Logger();\nconst orderService = new OrderService(logger);\n\n2. Eigenschaftsinjektion (Property Injection)
\n\nDie Abhängigkeit wird direkt in eine Eigenschaft des Objekts gesetzt, nachdem es erstellt wurde. Wird seltener verwendet, da es zu einer unvollständigen Initialisierung des Objekts führen kann.
\n\nclass EmailService {\n sendEmail(to: string, message: string) {\n console.log(`Email sent to ${to}: ${message}`);\n }\n}\n\nclass NotificationService {\n emailService!: EmailService; // Typzusicherung\n\n notifyUser(user: string) {\n this.emailService.sendEmail(user, 'Hello!');\n }\n}\n\nconst notification = new NotificationService();\nnotification.emailService = new EmailService(); // Eigenschaft setzen\nnotification.notifyUser('user@example.com');\n\n