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é.