Présentation des principaux design patterns en C++


précédentsommairesuivant

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-1. 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 :

Diagramme UML du DP observateur
Diagramme UML du pattern observateur

VII-2. Première implémentation

Grâce à l'analyse faite ci-dessus, une première implémentation du pattern observateur est tout simplement :

 
Sélectionnez

#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
 
Sélectionnez

#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-3. Remarque sur ce pattern

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


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2007 Côme David. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.