Architekturmuster in TypeScript: Wie man aufhört, "Spaghetti" zu schreiben und anfängt zu leben
\n\nHaben Sie jemals ein TypeScript-Projekt geöffnet, das Sie vor einem halben Jahr geschrieben haben, und mit Entsetzen festgestellt, dass Sie sich in Ihren eigenen Klassen nicht mehr zurechtfinden? Oder haben Sie vielleicht eine neue Funktion hinzugefügt, und die Anwendung ist an drei unerwarteten Stellen abgestürzt? Kommt Ihnen bekannt vor? Willkommen im Club der Entwickler, die einmal dachten "das reicht schon" – und dafür bezahlt haben.
\n\nTypeScript ist nicht nur ein Aufsatz über JavaScript mit Typen. Es ist ein mächtiges Werkzeug, mit dem Sie komplexe, skalierbare und wartbare Systeme erstellen können. Aber Typen allein retten Sie nicht vor dem architektonischen Chaos. Sie brauchen Architekturmuster – bewährte Rezepte, die helfen, den Code so zu organisieren, dass er verständlich, testbar und flexibel ist.
\n\nIn diesem Artikel analysieren wir fünf wichtige Architekturmuster in TypeScript, die am häufigsten in realen Projekten verwendet werden. Sie sehen Codebeispiele, verstehen, wo und warum jedes Muster angewendet wird, und können es sofort in Ihrer Arbeit einsetzen. Los geht's!
\n\n1. MVC (Model-View-Controller) – der Klassiker
\n\nBeginnen wir mit dem bekanntesten Muster. MVC in TypeScript ist die Aufteilung der Anwendung in drei Schichten:
\n\n- \n
- Model – Daten und Geschäftslogik. \n
- View – Darstellung (UI). \n
- Controller – verbindendes Glied, verarbeitet Benutzereingaben. \n
In TypeScript-Anwendungen ist MVC besonders gut, weil wir alle Schichten streng typisieren und Fehler bereits zur Kompilierzeit ausschließen können.
\n\nBeispiel: Einfache Aufgabenliste (Todo List)
\n\n// Model\ninterface Task {\n id: number;\n title: string;\n completed: boolean;\n}\n\nclass TaskModel {\n private tasks: Task[] = [];\n\n addTask(title: string): Task {\n const newTask: Task = {\n id: Date.now(),\n title,\n completed: false,\n };\n this.tasks.push(newTask);\n return newTask;\n }\n\n getTasks(): Task[] {\n return this.tasks;\n }\n}\n\n// View\nclass TaskView {\n render(tasks: Task[]): void {\n console.clear();\n tasks.forEach(task => {\n console.log(`${task.id}: [${task.completed ? 'x' : ' '}] ${task.title}`);\n });\n }\n}\n\n// Controller\nclass TaskController {\n constructor(\n private model: TaskModel,\n private view: TaskView\n ) {}\n\n addTask(title: string): void {\n this.model.addTask(title);\n this.view.render(this.model.getTasks());\n }\n\n showAll(): void {\n this.view.render(this.model.getTasks());\n }\n}\n\n// Verwendung\nconst model = new TaskModel();\nconst view = new TaskView();\nconst controller = new TaskController(model, view);\ncontroller.addTask('TypeScript-Muster lernen');\ncontroller.addTask('Artikel schreiben');\n\nWarum ist das cool? Sie können die View ändern (z. B. von der Konsole zu React wechseln), ohne das Model anzufassen. Sie können die Geschäftslogik getrennt vom UI testen. Die Architektur der TypeScript-Anwendung wird transparent.
\n\n2. Observer – der König der Ereignisse
\n\nWenn Ihre Anwendung von Ereignissen lebt (Klicks, Serverantworten, Timer), ist das Observer-Muster in TypeScript Ihr bester Freund. Es ermöglicht Objekten (Abonnenten), Änderungen eines anderen Objekts (Herausgebers) zu verfolgen.
\n\nBeispiel: Benachrichtigungssystem
\n\n// Schnittstelle des Beobachters\ninterface Observer {\n update(data: string): void;\n}\n\n// Herausgeber\nclass NewsPublisher {\n private observers: Observer[] = [];\n\n subscribe(observer: Observer): void {\n this.observers.push(observer);\n }\n\n unsubscribe(observer: Observer): void {\n this.observers = this.observers.filter(obs => obs !== observer);\n }\n\n publishNews(news: string): void {\n this.observers.forEach(obs => obs.update(news));\n }\n}\n\n// Konkreter Beobachter: E-Mail-Benachrichtigung\nclass Em