Event Bus und das Observer-Pattern in TypeScript
Das Observer-Pattern (Beobachter) und seine praktische Umsetzung – der Event Bus (Ereignisbus) – sind grundlegende Werkzeuge für die Erstellung lose gekoppelter, skalierbarer Anwendungen. In diesem Artikel werden wir detailliert erläutern, wie man einen Event Bus in TypeScript unter Verwendung strenger Typisierung implementiert und seine Anwendung in Bildungsprojekten betrachten.
Was ist das Observer-Pattern?
Das Observer-Pattern definiert eine "Eins-zu-Viele"-Beziehung zwischen Objekten. Wenn ein Objekt (Herausgeber, Subject) seinen Zustand ändert, werden alle abhängigen Objekte (Beobachter, Observers) automatisch benachrichtigt und aktualisiert. Dies vermeidet eine feste Kopplung der Komponenten.
Was ist ein Event Bus?
Ein Event Bus (Ereignisbus) ist ein zentralisierter Mechanismus, der das Observer-Pattern implementiert. Anstatt Beobachter direkt beim Herausgeber zu abonnieren, kommunizieren alle Komponenten über einen einzigen Bus. Eine Komponente kann:
- Ein bestimmtes Ereignis abonnieren (subscribe/listen).
- Ein Ereignis veröffentlichen (emit/publish), das an alle Abonnenten weitergeleitet wird.
- Ein Ereignis kündigen (unsubscribe), wenn es nicht mehr benötigt wird.
Dieser Ansatz eignet sich ideal für:
- Interaktion zwischen Modulen, die nichts voneinander wissen müssen.
- Verarbeitung von Benutzeraktionen (Klicks, Dateneingabe).
- Reaktion auf Zustandsänderungen der Anwendung (Daten laden, Fehler).
Implementierung eines Event Bus in TypeScript
TypeScript fügt ein leistungsstarkes Typsystem hinzu, das den Event Bus sicher und vorhersagbar macht. Wir werden einen typisierten Bus erstellen, bei dem jedes Ereignis eine streng definierte Menge von Argumenten hat.
1. Definition der Ereignistypen
Zuerst definieren wir ein Interface für die Zuordnung von Ereignisnamen und deren Argumenten:
// Definieren der Ereigniskarte: Schlüssel = Ereignisname, Wert = Argumenttypinterface EventMap { 'user:login': { userId: string; timestamp: number }; 'data:loaded': { items: string[] }; 'error:occurred': { message: string; code: number }; // Weitere Ereignisse können hinzugefügt werden}2. Erstellen der EventBus-Klasse
Jetzt erstellen wir die Klasse selbst unter Verwendung von Generics für strenge Typisierung:
type Listener<T> = (payload: T) => void;
class EventBus<T extends Record<string, unknown>> { private listeners: Map<keyof T, Set<Listener<unknown>>> = new Map();
// Ereignis abonnieren subscribe<K extends keyof T>(event: K, listener: Listener<T[K]>): () => void { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } const currentListeners = this.listeners.get(event)!; currentListeners.add(listener as Listener<unknown>);
// Rückgabe einer Funktion zum Kündigen return () => { currentListeners.delete(listener as Listener<unknown>); }; }
// Ereignis veröffentlichen emit<K extends keyof T>(event: K, payload: T[K]): void { const currentListeners = this.listeners.get(event); if (currentListeners) { currentListeners.forEach((listener) => { listener(payload); }); } }
// Alle Ereignisse kündigen (nützlich zur Bereinigung) clear(): void { this.listeners.clear(); }}
// Erstellen einer Bus-Instanz mit unserer Ereigniskarteexport const appEventBus = new EventBus<EventMap>();3. Beispiel zur Verwendung
Betrachten wir, wie verschiedene Module über den Event Bus interagieren können:
// Authentifizierungsmodulclass AuthService { login(userId: string) { // ... Anmeldelogik ... appEventBus.emit('use Auch in der Bibliothek
JSON-Verarbeitung in Kotlin: Bibliotheken und Codebeispiele
Eigen für C++: Vollständiger Leitfaden mit Beispielen
Kryptografie in Rust mit der RustCrypto-Bibliothek