Entwurfsmuster in TypeScript: Wann und warum
\n\nStellen Sie sich vor, Sie bauen ein Haus. Sie können die Ziegel beliebig setzen, und es wird wahrscheinlich nicht einstürzen. Aber wird es angenehm darin zu leben sein? Wohl kaum. Softwarearchitektur ist wie ein Bauprojekt, nur dass statt Ziegeln Klassen und Funktionen verwendet werden. Entwurfsmuster in TypeScript sind bewährte Blaupausen für typische Architekturaufgaben. Aber wie jedes Werkzeug müssen sie mit Bedacht eingesetzt werden.
\n\nIn diesem Artikel werden wir nicht die trockene Theorie aus den GoF-Büchern (Gang of Four) wiederholen. Stattdessen analysieren wir, welche Entwurfsmuster in TypeScript in der modernen Entwicklung wirklich benötigt werden, wann sie ein Projekt retten und wann sie nur Kopfschmerzen bereiten. Los geht's!
\n\nWarum ändert TypeScript die Spielregeln?
\n\nBevor wir in die Muster eintauchen, erinnern wir uns an die Hauptfunktion von TypeScript – die statische Typisierung. In JavaScript haben wir oft Muster verwendet, um die dynamische Natur der Sprache auszugleichen. Zum half der Factory Method dabei, die Komplexität der Objekterstellung zu verbergen. In TypeScript wird diese Komplexität oft einfach durch Interfaces und Generics gelöst.
\n\nDas bedeutet jedoch nicht, dass die Muster tot sind. Im Gegenteil, TypeScript macht sie ausdrucksstärker und sicherer. Der Compiler überprüft, ob Sie die Struktur korrekt implementiert haben, und verhindert dumme Fehler. Hier entfaltet TypeScript die Stärke der GoF-Muster.
\n\nTop 5 Muster, die Sie am häufigsten verwenden werden
\n\n1. Singleton – wenn einer gut ist, sind zwei überflüssig
\n\nWann verwenden: Sie benötigen genau eine Instanz einer Klasse für die gesamte Anwendung. Klassische Beispiele sind Logger, Anwendungskonfiguration oder Datenbankverbindungen.
\n\nWarum in TypeScript: TypeScript ermöglicht es, Singleton threadsicher und typsicher zu machen. Ohne ihn riskieren Sie, Dutzende von Objekten zu erzeugen, die Speicher verschwenden und den Zustand verwirren.
\n\nCodebeispiel:
\n\nclass DatabaseConnection {\n private static instance: DatabaseConnection;\n private connectionString: string;\n\n private constructor(connectionString: string) {\n this.connectionString = connectionString;\n // Simulation der Verbindung\n console.log(`Verbindung zu ${connectionString}`);\n }\n\n static getInstance(connectionString: string): DatabaseConnection {\n if (!DatabaseConnection.instance) {\n DatabaseConnection.instance = new DatabaseConnection(connectionString);\n }\n return DatabaseConnection.instance;\n }\n\n query(sql: string): void {\n console.log(`Ausführen der Abfrage: ${sql}`);\n }\n}\n\n// Verwendung\nconst db = DatabaseConnection.getInstance('postgres://localhost:5432/mydb');\ndb.query('SELECT * FROM users');\n\n// Versuch, eine weitere Verbindung zu erstellen, gibt dieselbe Instanz zurück\nconst db2 = DatabaseConnection.getInstance('postgres://localhost:5432/mydb');\nconsole.log(db === db2); // true\n\nAntimuster: Machen Sie Singleton nicht um des Singletons willen. Wenn ein Objekt in mehreren Instanzen mit unterschiedlichen Zuständen existieren kann (z. B. verschiedene Verbindungen zu verschiedenen Datenbanken), dann ist Singleton böse.
\n\n2. Factory Method – Objekterstellung ohne Schmerz
\n\nWann verwenden: Sie wissen nicht im Voraus, welches Objekt genau erstellt werden muss. Oder der Erstellungsprozess ist komplex und erfordert Logik.
\n\nWarum in TypeScript: Die Factory Method harmoniert hervorragend mit Interfaces und Generics. Sie erhalten Flexibilität ohne Typverlust.
\n\nCodebeispiel:
\n\n// Abstraktes Produkt\ninterface Notification {\n send(message: string): void;\n}\n\n// Konkrete Produkte\nclass EmailNotification implements Notification {\n send(message: string): void {\n console.log(`Sende Email: ${message}`);\n }