Event Bus et le pattern Observer en C++ : de la théorie à l'implémentation moderne
Dans le développement moderne en C++, le besoin d'un mécanisme flexible d'échange de messages entre des composants qui ne doivent pas se connaître directement se fait de plus en plus sentir. Le pattern Observer et son évolution — l'Event Bus — résolvent ce problème en permettant de construire des systèmes faiblement couplés et extensibles. Dans cet article, nous allons voir comment implémenter un Event Bus en C++ en utilisant la bibliothèque standard (C++17/20), et examiner des exemples pratiques.
Le pattern Observer : la base classique
Le pattern Observer définit une relation "un à plusieurs" entre des objets : lorsqu'un objet (le sujet) change d'état, tous les objets dépendants (les observateurs) en sont automatiquement notifiés. En C++, l'implémentation classique ressemble à ceci :
#include <iostream>#include <vector>#include <memory>#include <algorithm>
// Classe de base de l'observateurclass IObserver {public: virtual ~IObserver() = default; virtual void update(const std::string& message) = 0;};
// Sujet (éditeur)class Subject {private: std::vector<std::weak_ptr<IObserver>> observers_;public: void attach(std::shared_ptr<IObserver> observer) { observers_.push_back(observer); }
void detach(std::shared_ptr<IObserver> observer) { observers_.erase( std::remove_if(observers_.begin(), observers_.end(), [&observer](const std::weak_ptr<IObserver>& wp) { auto sp = wp.lock(); return !sp || sp == observer; }), observers_.end() ); }
void notify(const std::string& message) { for (auto it = observers_.begin(); it != observers_.end(); ) { if (auto sp = it->lock()) { sp->update(message); ++it; } else { it = observers_.erase(it); // supprime les références "mortes" } } }};
// Observateur concretclass ConcreteObserver : public IObserver {private: std::string name_;public: ConcreteObserver(const std::string& name) : name_(name) {} void update(const std::string& message) override { std::cout << "[" << name_ << "] a reçu : " << message << std::endl; }};
int main() { auto subject = std::make_shared<Subject>(); auto obs1 = std::make_shared<ConcreteObserver>("Observateur 1"); auto obs2 = std::make_shared<ConcreteObserver>("Observateur 2");
subject->attach(obs1); subject->attach(obs2); subject->notify("Événement déclenché !");
subject->detach(obs1); subject->notify("Uniquement pour le second");
return 0;}Ce code fonctionne, mais il a des limitations : tous les observateurs doivent hériter de IObserver, et les messages doivent être des chaînes de caractères. En pratique, un système plus flexible est nécessaire.
Event Bus : l'évolution moderne
L'Event Bus est un bus centralisé à travers lequel les composants peuvent publier des événements et s'y abonner. Les principales différences par rapport à l'Observer classique :
- Événements typés — chaque événement possède son propre type de données.
- Absence de hiérarchie rigide — les abonnés ne sont pas obligés d'hériter d'une interface commune.
- Bus global ou local — on peut avoir un seul Event Bus pour toute l'application ou plusieurs bus isolés.
Implémentation d'un Event Bus en C++17/20 avec std::any
Utilisons std::any pour stocker des données de n'importe quel type et std::function pour les gestionnaires. Pour séparer les événements par type, appliquons std::type_index.
#include <iostream>#include <any>#include <function