Ce tutorial a pour but de proposer une implementation en C++ du patron de conception Observer / Observable. Comme il s’agit a la base d’un patron de conception, il en existe par definition beaucoup d’implementations specifiques au domaine.
Ici nous allons proposer une implementation « generique » qui sera utilisable dans la majorite des cas.
Introduction
La relation Observer/Observable est un concept assez simple, le principe repose sur le fait de pouvoir informer automatiquement des objets (Observeurs) des modification d’un autre (l’Observable). Nous prendrons comme cas « d’école » l’exemple d’un VU mètre system. On supposera avoir une classe CPU permettant de connaitre la charge cpu, une classe FileSystem donnant accès aux informations concernant les disques et enfin, une classe charger d’afficher l’état de notre machine i.e. la charge CPU et l’occupation des disques.
La classe Observer
L’observer est un objet « simple », être observeur defini uniquement le fait de pouvoir être mis a jour par un objet extérieur. Ceci se traduit par avoir une méthode de type update.
Ceci nous mène à une premiere définition de l’interface Observer :
struct Observer { virtual void update() = 0; }; |
Rendre un objet observateur d’un sujet, se traduira alors par une implémentation de cette interface. ex :
class VUMeter : public Observer { void update(void){ // mis a jour car un sujet a changé d'etat } }; |
La première limitation de cette proposition vient du fait que l’on ne prend pas en compte le type du sujet observe dans l’interface. Ceci a l’utilisation, obligera en plus d’hériter de l’interface, de stocker dans ses attributs une référence au sujet. ex :
class VUMeter : public Observer{ private : Subject *subjet; public : void update(void){ // subject a change subject->getValues()... } }; |
une première solution, est de passer en paramètre la classe Observable dans la méthode update.
void update(Observable *subject){ // } |
cette solution est presque viable, mais va obliger :
- soit a implementer dans l’observable, une interface generique pour récuperer les information d’un sujet « abstrait ». Il nous faudra récuperer la charge CPU, l’occupation du disque…
- soit a « dynamic_caster » le sujet de type Observable en son reel type
void update(Observable *subjet){ if((CPU *cpu = dynamic_cast(subject)) != NULL){ cpu->getLoad()... } else if((FileSystem* fs = dynamic_cast(subject)) != NULL){ fs->getFreeSpace(); } } |
Cette solution n’est pas des plus élegantes. Une derniere alternative plus « propre » serait de posseder une méthode update prenant en paremetre un CPU* et une autre prenant un FileSystem*
void update(FileSystem*); void update(CPU*); |
Cette solution nous garantie une separation propre du code. Pour l’implementer, nous allons devoir passer par l’utilisation de template. Le principe : spécifier le type reel observé dans l’interface Observer
template struct Observer{ virtual void update(Subject *) = 0; }; |
Et voici maintenant l’utilisation :
class VUMeter : public Observer, public Observer{ public: void update(FileSystem* fs){ fs->getFreeSpace(... } void update(CPU* cpu){ cpu->... } }; |
Et voila le travail, nous avons un observeur capable d’observer plusieurs sujets. Ils nous reste a implémenter l’autre partie : l’Observable
La classe Observable
Nous devons ici definir la classe de laquelle vont deriver CPU et FileSystem pour être observable. Les besoins d’un Observable sont :
- lui attacher un observateur
- le détacher
- notifier tous les observateurs
- stocker ses observateurs
class Observable{ std::set _observers; void attach(Observer *o); // insertion dans le set void detach(Observer *o); // suppression du set void notify(void){ // updater tous les observers dans le set ... obs->update(...) } }; |
Cette implémentation ne fonctionne pas a cause de la « templatisation » de la class Observer qui prend, déjà, pour la méthode update le type réel du sujet (ex : FileSystem) mais aussi par le fait que les observeurs n’héritent pas de Observer, mais Observer.
Dans la classe Obserable, nous devons donc connaitre le type reel du sujet.
Encore une fois les template vont permettre de nous en sortir :
template class Observable { public: ~Observable(void); void attach(Observer *o); void detach(Observer *o); void notify(void); private: std::set<Observer *> _observers; }; |
A l’utilisation, ceci donne :
class FileSystem : public Observable{ ... void some_func(){ ... notify(); // on change d'etat, informer les observateurs } ... }; VUMeter vum; FileSystem fs; CPU cpu; cpu.attach(&vum); fs.attach(&vum); |
Et Voilà, c’est fini!
Cet article provient du tutorial publié par Twxs le 03/11/2004 sur l’ancien site de Coder-Studio.


#1 by fiwedding - juillet 30th, 2010 at 03:04
You may have not intended to do so, but I think you have managed to express the state of mind that a lot of people are in. The sense of wanting to help, but not knowing how or where, is something a lot of us are going through.