Event Bus et le pattern Observer en TypeScript
Le pattern Observer (Observateur) et son implémentation pratique — l'Event Bus (Bus d'événements) — sont des outils fondamentaux pour construire des applications faiblement couplées et évolutives. Dans cet article, nous allons détailler comment implémenter un Event Bus en TypeScript, en utilisant un typage strict, et examiner son utilisation dans des projets éducatifs.
Qu'est-ce que le pattern Observer ?
Le pattern Observer définit une relation "un à plusieurs" entre les objets. Lorsqu'un objet (l'éditeur, subject) modifie son état, tous les objets qui en dépendent (les observateurs, observers) sont automatiquement notifiés et mis à jour. Cela permet d'éviter un couplage rigide entre les composants.
Qu'est-ce qu'un Event Bus ?
Un Event Bus (bus d'événements) est un mécanisme centralisé qui implémente le pattern Observer. Au lieu d'abonner directement les observateurs à l'éditeur, tous les composants communiquent via un bus unique. Un composant peut :
- S'abonner (subscribe/listen) à un événement spécifique.
- Publier (emit/publish) un événement, qui sera transmis à tous les abonnés.
- Se désabonner (unsubscribe) d'un événement lorsque ce n'est plus nécessaire.
Cette approche est idéale pour :
- L'interaction entre des modules qui ne doivent pas se connaître.
- Le traitement des actions utilisateur (clics, saisie de données).
- La réaction aux changements d'état de l'application (chargement de données, erreurs).
Implémentation d'un Event Bus en TypeScript
TypeScript ajoute un système de types puissant, ce qui rend l'Event Bus sûr et prévisible. Nous allons créer un bus typé, où chaque événement possède un ensemble d'arguments strictement défini.
1. Définition des types d'événements
Commençons par définir une interface pour mapper les noms d'événements et leurs arguments :
// Définissons une carte d'événements : clé = nom de l'événement, valeur = type des argumentsinterface EventMap { 'user:login': { userId: string; timestamp: number }; 'data:loaded': { items: string[] }; 'error:occurred': { message: string; code: number }; // On peut ajouter n'importe quel autre événement}2. Création de la classe EventBus
Créons maintenant la classe elle-même, en utilisant les génériques pour un typage strict :
type Listener<T> = (payload: T) => void;
class EventBus<T extends Record<string, unknown>> { private listeners: Map<keyof T, Set<Listener<unknown>>> = new Map();
// Abonnement à un événement 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>);
// Retourne une fonction pour se désabonner return () => { currentListeners.delete(listener as Listener<unknown>); }; }
// Publication d'un événement emit<K extends keyof T>(event: K, payload: T[K]): void { const currentListeners = this.listeners.get(event); if (currentListeners) { currentListeners.forEach((listener) => { listener(payload); }); } }
// Désabonnement de tous les événements (utile pour le nettoyage) clear(): void { this.listeners.clear(); }}
// Créons une instance du bus avec notre carte d'événementsexport const appEventBus = new EventBus<EventMap>();3. Exemple d'utilisation
Voyons comment différents modules peuvent interagir via l'Event Bus :
// Module d'authentificationclass AuthService { login(userId: string) { // ... logique de connexion ... appEventBus.emit('use Aussi dans la bibliothèque
Planificateur de tâches Java : Quartz Scheduler – guide complet
Drogon C++ : guide complet avec exemples pratiques