Event Bus et le pattern Observer en TypeScript : de la théorie à la pratique
\n\nLe développement web moderne nécessite une architecture flexible et évolutive. Deux patterns clés qui aident à organiser l'interaction entre les composants sont le Event Bus et l'Observer. Dans cet article, nous allons analyser leur implémentation en TypeScript, leurs avantages et des scénarios d'utilisation pratiques.
\n\nQu'est-ce que le pattern Observer ?
\n\nLe pattern Observer (Observateur) est un modèle de conception comportemental qui définit une relation "un à plusieurs" entre les objets. Lorsqu'un objet (l'éditeur) modifie son état, tous les objets dépendants (les abonnés) sont automatiquement notifiés et mis à jour.
\n\nLes éléments principaux du pattern :
\n- \n
- Subject (Éditeur) — l'objet qui suit les modifications et notifie les abonnés. \n
- Observer (Observateur) — l'objet qui s'abonne aux modifications de l'éditeur. \n
- Event Bus (Bus d'événements) — un mécanisme centralisé qui gère les abonnements et l'envoi d'événements entre les composants. \n
Implémentation de l'Event Bus en TypeScript
\n\nTypeScript offre d'excellentes possibilités pour un typage strict, ce qui rend l'implémentation de l'Event Bus sûre et prévisible. Examinons une implémentation de base :
\n\ntype EventHandler<T = any> = (payload: T) => void;\n\nclass EventBus {\n private listeners: Map<string, EventHandler[]> = new Map();\n\n // Abonnement à un événement\n subscribe<T>(event: string, handler: EventHandler<T>): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, []);\n }\n this.listeners.get(event)!.push(handler);\n }\n\n // Désabonnement d'un événement\n unsubscribe<T>(event: string, handler: EventHandler<T>): void {\n const handlers = this.listeners.get(event);\n if (handlers) {\n this.listeners.set(\n event,\n handlers.filter(h => h !== handler)\n );\n }\n }\n\n // Publication d'un événement\n emit<T>(event: string, payload: T): void {\n const handlers = this.listeners.get(event);\n if (handlers) {\n handlers.forEach(handler => handler(payload));\n }\n }\n\n // Nettoyage de tous les abonnements\n clear(): void {\n this.listeners.clear();\n }\n}\n\nexport const eventBus = new EventBus();\n\nExemple d'utilisation avec des événements typés
\n\nPour améliorer la sécurité des types, on peut définir des interfaces d'événements :
\n\n// Définition des types d'événements\ninterface AppEvents {\n 'user:login': { userId: string; timestamp: number };\n 'user:logout': { userId: string };\n 'data:update': { id: string; data: unknown };\n 'notification:show': { message: string; type: 'success' | 'error' | 'info' };\n}\n\n// Création d'un bus typé\nclass TypedEventBus {\n private listeners: Map<string, Function[]> = new Map();\n\n on<K extends keyof AppEvents>(\n event: K,\n handler: (payload: AppEvents[K]) => void\n ): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, []);\n }\n this.listeners.get(event)!.push(handler);\n }\n\n emit<K extends keyof AppEvents>(\n event: K,\n payload: AppEvents[K]\n ): void {\n const handlers = this.listeners.get(event);\n if (handlers) {\n handlers.forEach(handler => handler(payload));\n }\n }\n\n off<K extends keyof AppEvents>(\n event: K,\n handler: (payload: AppEvents[K]) => void\n ): void {\n const handlers = this.listeners.get(event);\n if (handlers) {\n this.listeners.set(\n event,\n handlers.filter(h => h !== handler)\n );\n }\n }\n}\n\nexport const typedBus = new TypedEventBus();\n\nExemple pratique : système de notifications
\n\nExaminons un scénario réel — la création d'un système de notifications utilisant l'Event Bus :