Implémentation du Patron de Conception Observateur en C++

Introduction au Patron Observateur

Le patron de conception Observateur (connu également sous le nom de "Publication/Abonnement") est une approche comportemantale qui établit une dépendance un-à-plusieurs entre des objets. Lorsqu'un objet, appelé "sujet" ou "éditeur", change d'état, tous ses dépendants, appelés "observateurs" ou "abonnés", sont automatiquement notifiés et mis à jour.

Pour illustrer ce concept, imaginez un service de diffusion de nouvelles en ligne. Si vous vous abonnez à un média spécifique, celui-ci vous enverra automatiquement des notifications chaque fois qu'un nouvel article est publié. Dans ce scénario, le média est le "sujet" et vous, en tant qu'abonné, êtes un "observateur". L'essence du patron Observateur réside donc dans la relation entre un Sujet et un ou plusieurs Observateurs.

Structure du Code du Patron Observateur

L'implémentation de ce patron en C++ implique généralement la définition d'interfaces ou de classes de base abstraites pour le Sujet et l'Observateur, ainsi que des implémentations concrètes pour chacun d'eux.

Définition du Sujet (Éditeur)

Le Sujet est l'objet qui maintient une liste de ses dépendants (les Observateurs) et les notifie de tout changement d'état. Il fournit des méthodes pour attacher et détacher des observateurs.

Sujet.hpp (Interface de l'Éditeur)

#ifndef SUJET_HPP
#define SUJET_HPP

#include <string>
#include <vector>
#include <algorithm> // Pour std::remove

// Déclaration anticipée pour éviter les dépendances circulaires
class Observateur; 

/**
 * @brief Classe abstraite Sujet, représentant l'éditeur d'informations.
 *        Elle gère la liste des abonnés et la notification.
 */
class Sujet {
public:
    /**
     * @brief Attache un observateur à la liste des abonnés.
     * @param obs Pointeur vers l'observateur à attacher.
     */
    void attacher(Observateur* obs);

    /**
     * @brief Détache un observateur de la liste des abonnés.
     * @param obs Pointeur vers l'observateur à détacher.
     */
    void detacher(Observateur* obs);

    /**
     * @brief Méthode virtuelle pure pour notifier tous les observateurs.
     * @param message Le message ou l'information à transmettre.
     */
    virtual void notifier(const std::string& message) = 0;

    /**
     * @brief Destructeur virtuel par défaut.
     */
    virtual ~Sujet() = default;

protected:
    // Liste des pointeurs vers les observateurs abonnés.
    std::vector<Observateur*> _observateurs; 
};

#endif // SUJET_HPP

Sujet.cpp (Implémentation de l'Éditeur)

#include "Sujet.hpp"

// Note: Observateur.hpp n'est pas nécessaire ici car nous ne manipulons que des pointeurs d'Observateur.

/**
 * @brief Attache un observateur au sujet.
 * @param obs Le pointeur vers l'observateur à ajouter.
 */
void Sujet::attacher(Observateur* obs) {
    _observateurs.push_back(obs);
}

/**
 * @brief Détache un observateur du sujet.
 * @param obs Le pointeur vers l'observateur à retirer.
 */
void Sujet::detacher(Observateur* obs) {
    // Utilise l'idiome erase-remove pour supprimer efficacement un élément d'un std::vector.
    _observateurs.erase(std::remove(_observateurs.begin(), _observateurs.end(), obs), _observateurs.end());
}

Définition de l'Observateur (Abonné)

L'Observateur est l'objet qui s'intéresse aux changements d'état du Sujet et est notifié lorsque ces changements se produisent. Il doit implémenter une méthode de mise à jour.

Observateur.hpp (Interface de l'Abonné)

#ifndef OBSERVATEUR_HPP
#define OBSERVATEUR_HPP

#include <string>
#include <iostream>

// Déclaration anticipée pour éviter les dépendances circulaires
class Sujet;

/**
 * @brief Classe abstraite Observateur, représentant un abonné aux informations.
 *        Elle définit l'interface pour la réception des notifications.
 */
class Observateur {
public:
    /**
     * @brief Constructeur de l'Observateur. S'abonne automatiquement au sujet passé.
     * @param sujet Pointeur vers le sujet auquel s'abonner.
     * @param nom Nom de l'observateur.
     */
    Observateur(Sujet* sujet, const std::string& nom);

    /**
     * @brief Désabonne cet observateur de son sujet actuel.
     */
    void desabonner();

    /**
     * @brief Méthode virtuelle pure appelée par le sujet pour notifier un changement.
     * @param information L'information ou le message mis à jour.
     */
    virtual void mettreAJour(const std::string& information) = 0;

    /**
     * @brief Destructeur virtuel par défaut.
     */
    virtual ~Observateur() = default;

protected:
    Sujet* _sujetSuivi;          // Pointeur vers le sujet que cet observateur suit.
    std::string _nomObservateur; // Nom de l'observateur pour identification.
};

#endif // OBSERVATEUR_HPP

Observateur.cpp (Implémentation de l'Abonné)

#include "Observateur.hpp"
#include "Sujet.hpp" // Nécessaire pour appeler les méthodes attacher/detacher du Sujet.

/**
 * @brief Constructeur de l'Observateur. Tente de s'abonner au sujet fourni.
 * @param sujet Pointeur vers le sujet.
 * @param nom Le nom de l'observateur.
 */
Observateur::Observateur(Sujet* sujet, const std::string& nom)
    : _sujetSuivi(sujet), _nomObservateur(nom) {
    if (_sujetSuivi) {
        _sujetSuivi->attacher(this);
    }
}

/**
 * @brief Désabonne l'observateur du sujet qu'il suit.
 */
void Observateur::desabonner() {
    if (_sujetSuivi) {
        _sujetSuivi->detacher(this);
        _sujetSuivi = nullptr; // Supprime la référence au sujet après le désabonnement.
    }
}

Sujets Concrets (Éditeurs Spécifiqeus)

Ces classes héritent de Sujet et implémentent la méthode notifier pour diffuser les messages spécifiques à leur type d'information.

EditeurConcrete.hpp

#ifndef EDITEUR_CONCRET_HPP
#define EDITEUR_CONCRET_HPP

#include "Sujet.hpp"

/**
 * @brief Editeur concret pour les nouvelles générales.
 */
class EditeurNouvelles : public Sujet {
public:
    /**
     * @brief Notifie les observateurs des nouvelles générales.
     * @param actualite Le contenu de l'actualité à diffuser.
     */
    void notifier(const std::string& actualite) override;
};

/**
 * @brief Editeur concret pour les potins et divertissements.
 */
class EditeurPotins : public Sujet {
public:
    /**
     * @brief Notifie les observateurs des potins.
     * @param ragot Le contenu du ragot à diffuser.
     */
    void notifier(const std::string& ragot) override;
};

#endif // EDITEUR_CONCRET_HPP

EditeurConcrete.cpp

#include "EditeurConcrete.hpp"
#include "Observateur.hpp" // Nécessaire pour appeler mettreAJour() sur les observateurs.

/**
 * @brief Implémentation de la notification pour les nouvelles générales.
 *        Parcourt tous les abonnés et les met à jour avec l'actualité.
 * @param actualite Le message d'actualité.
 */
void EditeurNouvelles::notifier(const std::string& actualite) {
    std::cout << "--- Publication de Nouvelles Générales (" << _observateurs.size() << " abonnés) ---" << std::endl;
    for (Observateur* obs : _observateurs) {
        obs->mettreAJour(actualite);
    }
}

/**
 * @brief Implémentation de la notification pour les potins.
 *        Parcourt tous les abonnés et les met à jour avec le ragot.
 * @param ragot Le message de potin.
 */
void EditeurPotins::notifier(const std::string& ragot) {
    std::cout << "--- Publication de Potins Célèbres (" << _observateurs.size() << " abonnés) ---" << std::endl;
    for (Observateur* obs : _observateurs) {
        obs->mettreAJour(ragot);
    }
}

Observateurs Concrets (Abonnés Spécifiques)

Ces classes héritent de Observateur et implémentent la méthode mettreAJour pour réagir spécifiquement aux notifications.

LecteurConcrete.hpp

#ifndef LECTEUR_CONCRET_HPP
#define LECTEUR_CONCRET_HPP

#include "Observateur.hpp"

/**
 * @brief Observateur concret représentant un lecteur fidèle.
 */
class LecteurFidele : public Observateur {
public:
    // Hérite le constructeur de la classe de base Observateur.
    using Observateur::Observateur; 

    /**
     * @brief Réaction spécifique du lecteur fidèle à une nouvelle information.
     * @param information L'information reçue.
     */
    void mettreAJour(const std::string& information) override;
};

/**
 * @brief Observateur concret représentant un lecteur curieux.
 */
class LecteurCurieux : public Observateur {
public:
    // Hérite le constructeur de la classe de base Observateur.
    using Observateur::Observateur;

    /**
     * @brief Réaction spécifique du lecteur curieux à une nouvelle information.
     * @param information L'information reçue.
     */
    void mettreAJour(const std::string& information) override;
};

#endif // LECTEUR_CONCRET_HPP

LecteurConcrete.cpp

#include "LecteurConcrete.hpp"

/**
 * @brief Affiche l'information reçue par un lecteur fidèle.
 * @param information Le message à traiter.
 */
void LecteurFidele::mettreAJour(const std::string& information) {
    std::cout << "[" << _nomObservateur << " - Lecteur Fidele] reçoit: \"" << information << "\"" << std::endl;
}

/**
 * @brief Affiche l'information reçue par un lecteur curieux.
 * @param information Le message à traiter.
 */
void LecteurCurieux::mettreAJour(const std::string& information) {
    std::cout << "[" << _nomObservateur << " - Lecteur Curieux] est informé: \"" << information << "\"" << std::endl;
}

Exemple d'Utilisation (main.cpp)

Voici comment créer des sujets et des observateurs, puis interagir avec eux pour démontrer le fonctionnement du patron.

main.cpp

#include <iostream>
#include "EditeurConcrete.hpp"
#include "LecteurConcrete.hpp"

int main() {
    // 1. Création des sujets (éditeurs de contenu)
    Sujet* editeurGeneral = new EditeurNouvelles();
    Sujet* editeurDivertissement = new EditeurPotins();

    // 2. Création des observateurs (lecteurs) et abonnement automatique via le constructeur
    Observateur* lecteurAlice = new LecteurFidele(editeurGeneral, "Alice");
    Observateur* lecteurBob = new LecteurCurieux(editeurDivertissement, "Bob");
    Observateur* lecteurCharlie = new LecteurFidele(editeurGeneral, "Charlie"); // Charlie s'abonne aux nouvelles générales
    Observateur* lecteurDavid = new LecteurCurieux(editeurDivertissement, "David"); // David s'abonne aux potins

    std::cout << "\n--- Première vague de publications ---" << std::endl;
    editeurGeneral->notifier("La conférence technologique annuelle a débuté à Paris.");
    editeurDivertissement->notifier("Une célébrité majeure a été aperçue dans une station de ski secrète.");

    std::cout << "\n--- Désabonnement de Charlie ---" << std::endl;
    lecteurCharlie->desabonner(); // Charlie décide de se désabonner

    std::cout << "\n--- Deuxième vague de publications ---" << std::endl;
    editeurGeneral->notifier("Nouvelles avancées en intelligence artificielle présentées."); // Charlie ne reçoit plus cette notification
    editeurDivertissement->notifier("Rumeurs de mariage royal démenties par le palais.");

    // 3. Nettoyage des ressources allouées dynamiquement
    delete editeurGeneral;
    delete editeurDivertissement;
    delete lecteurAlice;
    delete lecteurBob;
    delete lecteurCharlie; // Même désabonné, l'objet Observateur doit être supprimé.
    delete lecteurDavid;

    return 0;
}

Étiquettes: C++ Patron de Conception Observateur Programmation orientée objet

Publié le 26 juillet à 06h09