VII. L'observateur▲
Définition : L'observateur est une classe dont le rôle est d'être averti quand l'une des classes qu'elle observe change.
VII-A. Pourquoi observer ?▲
Imaginez que vous deviez réaliser un programme de surveillance de données météorologiques pour un agriculteur qui doit surveiller la pression atmosphérique et la température. Comment réaliser ceci ? On pourrait créer une classe qui interrogerait à intervalles réguliers le thermomètre et le baromètre. Mais ce n'est pas la meilleure des solutions. En effet, on va consommer du CPU pour rien si les valeurs relevées par les instruments ne changent pas.
Il faudrait plutôt faire l'inverse : notre programme doit être averti à chaque changement de valeur et c'est précisément ce que propose l'observateur. À chaque objet observé on attache un observateur qui va être averti via une fonction Update lors d'un changement de valeur. Pour lancer cette notification, une fonction Notify est indispensable dans l'objet observé en plus que celles pour ajouter/enlever un observateur. Ainsi, on voit donc que le pattern a pour diagramme :
VII-B. Première implémentation▲
Grâce à l'analyse faite ci-dessus, une première implémentation du pattern observateur est tout simplement :
#ifndef OBS_H
#define OBS_H
#include
<iostream>
#include
<list>
#include
<iterator>
#include
<algorithm>
typedef
int
Info;
class
Observable;
class
Observateur
{
protected
:
std::
list<
Observable*>
m_list;
typedef
std::
list<
Observable*>
::
iterator iterator;
typedef
std::
list<
Observable*>
::
const_iterator const_iterator;
virtual
~
Observateur() =
0
;
public
:
virtual
void
Update(const
Observable*
observable) const
;
void
AddObs(Observable*
obs);
void
DelObs(Observable*
obs);
}
;
class
Observable
{
std::
list<
Observateur*>
m_list;
typedef
std::
list<
Observateur*>
::
iterator iterator;
typedef
std::
list<
Observateur*>
::
const_iterator const_iterator;
public
:
void
AddObs( Observateur*
obs);
void
DelObs(Observateur*
obs);
virtual
Info Statut(void
) const
=
0
;
virtual
~
Observable();
protected
:
void
Notify(void
);
}
;
class
Barometre : public
Observable
{
int
pression;
public
:
void
Change(int
valeur);
int
Statut(void
) const
;
}
;
class
Thermometre : public
Observable
{
int
temperature;
public
:
void
Change(int
valeur);
Info Statut(void
) const
;
}
;
class
MeteoFrance : public
Observateur
{
}
;
#endif
#include
<iostream>
#include
<vector>
#include
<iterator>
#include
<algorithm>
#include
"obs.h"
using
namespace
std;
void
Observateur::
Update(const
Observable*
observable) const
{
//on affiche l'état de la variable
cout<<
observable->
Statut()<<
endl;
}
Observateur::
~
Observateur()
{
//pour chaque objet observé,
//on lui dit qu'on doit supprimer l'observateur courant
const_iterator ite =
m_list.end();
for
(iterator itb =
m_list.begin(); itb !=
ite; ++
itb)
{
(*
itb)->
DelObs(this
);
}
}
void
Observateur::
AddObs( Observable*
obs)
{
m_list.push_back(obs);
}
void
Observateur::
DelObs(Observable*
obs)
{
//on enlève l'objet observé.
iterator it =
std::
find(m_list.begin(),m_list.end(),obs);
if
(it !=
m_list.end())
m_list.erase(it);
}
void
Observable::
AddObs( Observateur*
obs)
{
//on ajoute l'observateur à notre liste
m_list.push_back(obs);
//et on lui donne un nouvel objet observé.
obs->
AddObs(this
);
}
void
Observable::
DelObs(Observateur*
obs)
{
//même chose que dans Observateur::DelObs
iterator it =
find(m_list.begin(),m_list.end(),obs);
if
(it !=
m_list.end())
m_list.erase(it);
}
Observable::
~
Observable()
{
//même chose qu'avec Observateur::~Observateur
iterator itb =
m_list.begin();
const_iterator ite =
m_list.end();
for
(; itb !=
ite; ++
itb)
{
(*
itb)->
DelObs(this
);
}
}
void
Observable::
Notify(void
)
{
//on prévient chaque observateur que l'on change de valeur
iterator itb =
m_list.begin();
const_iterator ite =
m_list.end();
for
(; itb !=
ite; ++
itb)
{
(*
itb)->
Update(this
);
}
}
void
Barometre::
Change(int
valeur)
{
pression =
valeur;
Notify();
}
int
Barometre::
Statut(void
) const
{
return
pression;
}
void
Thermometre::
Change(int
valeur)
{
temperature =
valeur;
Notify();
}
Info Thermometre::
Statut(void
) const
{
return
temperature;
}
int
main(void
)
{
Barometre barometre;
Thermometre thermometre;
//un faux bloc pour limiter la portée de la station
{
MeteoFrance station;
thermometre.AddObs(&
station);
barometre.AddObs(&
station);
thermometre.Change(31
);
barometre.Change(975
);
}
thermometre.Change(45
);
return
0
;
}
Dans le code en lui-même, je pense qu'il n'y a rien de bien difficile. Par contre, ce qui est un peu plus dur à comprendre, c'est le pattern en lui-même. À chaque changement de valeur dans une classe observée, on appelle la méthode Notify qui va signaler à tous les observateurs de l'objet "J'ai changé de valeur ! Demande-moi ma nouvelle valeur". Mais pour l'observateur qui est ce "J'" ? Comment peut-il le savoir ? C'est pourquoi il y a le passage d'un pointeur vers l'objet observé lors de l'appel à Update.
Ici info est un typedef. Mais dans un cas réel on pourrait plutôt passer par des classes du type InfoBarometre ou InfoThermometre dérivant toutes d'une interface InfoBase avec un opérateur appelant une fonction Afficher polymorphique.
VII-C. Remarque sur ce pattern▲
VII-C-1. Tirer/Pousser▲
Il existe deux manières de récupérer l'information dans la méthode Update. Une première consiste à "tirer" l'information de l'objet en appelant une méthode qui va nous donner l'information. La deuxième consiste au contraire à "pousser". C'est l'objet observé qui va pousser l'information jusqu'à l'observateur. Ici, j'ai donc adopté la méthode de tirer qui est plus simple et permet plus de modularité.