32 votes

Problèmes de mise en œuvre du modèle "Observateur"

J'ai rencontré un problème intéressant tout en mettant en œuvre le modèle Observateur du C++ et STL. Considérons cet exemple classique:

class Observer {
public:
   virtual void notify() = 0;
};

class Subject {
public:
   void addObserver( Observer* );
   void remObserver( Observer* );
private:
   void notifyAll();
};

void Subject::notifyAll() {
   for (all registered observers) { observer->notify(); }
}

Cet exemple peut être trouvé dans tous les livres sur les modèles de conception. Malheureusement, dans la vie réelle, les systèmes sont plus complexes, voici donc le premier problème: certains observateurs décider d'ajouter d'autres observateurs de l'Objet de la notification. Cela invalide la boucle "for" et tous les itérateurs, que j'utilise. La solution est plutôt simple - je faire un instantané de la observateurs inscrits de la liste et effectuer une itération sur l'instantané. L'ajout de nouveaux observateurs n'invalide pas l'instantané, donc, tout semble ok. Mais ici se pose un autre problème: les observateurs décider de détruire eux-mêmes sur d'en être informés. Pire encore, un seul observateur peut décider de détruire tous les autres observateurs (ils sont contrôlés à partir de l'scripts), et qui invalide la fois la file d'attente et un instantané. Je me retrouve à parcourir de-alloués pointeurs.

Ma question est comment dois-je gérer les situations, lorsque les observateurs se tuer les uns les autres? Il n'existe aucun prêt-à-utiliser des modèles? J'ai toujours pensé que "Observateur" est la méthode la plus simple motif de conception dans le monde, mais maintenant il semble qu'il n'est pas facile à mettre en œuvre correctement...

Merci à tous pour votre intérêt. Laissez-nous un résumé des décisions:

[1] "ne fais pas ça" Désolé, mais c'est un must. Les observateurs sont contrôlées à partir de scripts et sont récupérées. Je ne peux pas contrôler la collecte des ordures, afin de prévenir leur de-la répartition;

[2] "Utiliser boost::signal" les plus prometteuses de La décision, mais je ne peut pas introduire de boost sur le projet, de telles décisions doivent être prises par le chef de projet uniquement (nous sommes écrit, en vertu de la Playstation);

[3] "l'Utilisation partagée__ptr" Qui va empêcher les observateurs de l'allocation. Certains sous-systèmes peuvent compter sur la mémoire de la piscine de nettoyage, donc je ne pense pas que je peux utiliser shared_ptr.

[4] "Reporter observateur de libération de la mémoire" de la File d'attente des observateurs pour l'enlèvement tout en informant, puis utilisez le deuxième cycle pour les supprimer. Malheureusement, je ne peux pas empêcher la libération de la mémoire, donc j'utilise un truc de conditionnement de l'observateur avec une sorte de "adaptateur", en gardant en fait la liste des "adaptateurs". Sur destructeur, les observateurs annuler l'affectation de leurs adaptateurs, alors je prends mon deuxième cycle de détruire vide adaptateurs.

p.s. c'est ok, que j'ai modifier ma question de résumer tous les post? Je suis noob sur StackOverflow...

14voto

T.E.D. Points 26829

Question très intéressante.

Essaye ça:

  1. Modifiez remObserver pour annuler l'entrée, plutôt que de simplement la supprimer (et invalider les itérateurs de liste).
  2. Modifiez votre boucle notifyAll pour qu'elle soit:

    pour (tous les observateurs enregistrés) {si (observateur) observateur-> notifier (); }

  3. Ajoutez une autre boucle à la fin de notifyAll pour supprimer toutes les entrées nulles de votre liste d'observateurs

7voto

Todd Gardner Points 8688

Personnellement, j'utilise boost::signaux pour mettre en œuvre ma qualité d'observateurs; je vais vérifier, mais je crois qu'il gère les scénarios ci-dessus (édité: il a trouvé, voir "Quand les déconnexions se produisent"). Il simplifie la mise en œuvre, et il ne repose pas sur la création de la classe personnalisée:

class Subject {
public:
   boost::signals::connection addObserver( const boost::function<void ()>& func )
   { return sig.connect(func); }

private:
   boost::signal<void ()> sig;

   void notifyAll() { sig(); }
};

void some_func() { /* impl */ }

int main() {
   Subject foo;
   boost::signals::connection c = foo.addObserver(boost::bind(&some_func));

   c.disconnect(); // remove yourself.
}

6voto

Lee Points 6659

Un homme va chez le médecin et dit: "Doc quand je lève mon bras comme ça ça fait vraiment mal!" Le médecin dit: "Ne fais pas ça."

La solution la plus simple est de travailler avec votre équipe et de leur dire de ne pas le faire. Si les observateurs "ont vraiment besoin" de se tuer, ou de tuer tous les observateurs, alors planifiez l'action pour la fin de la notification. Ou, mieux encore, changez la fonction remObserver pour savoir s'il y a un processus de notification en cours et placez simplement les suppressions en file d'attente lorsque tout est terminé.

5voto

TheUndeadFish Points 4278

Voici une variation sur l'idée de T. E. D. déjà présenté.

Aussi longtemps que remObserver pouvez null, une entrée au lieu de supprimer immédiatement, alors vous pourriez mettre en œuvre notifyAll que:

void Subject::notifyAll()
{
    list<Observer*>::iterator i = m_Observers.begin();
    while(i != m_Observers.end())
    {
        Observer* observer = *i;
        if(observer)
        {
            observer->notify();
            ++i;
        }
        else
        {
            i = m_Observers.erase(i);
        }
    }
}

Cela évite la nécessité d'un deuxième nettoyage de la boucle. Toutefois cela signifie que si certains notify() appel déclenche la suppression de lui-même ou un observateur situé plus haut dans la liste, puis de la suppression effective de l'élément de la liste sera différée jusqu'à la prochaine notifyAll(). Mais tant que toutes les fonctions qui opèrent sur la liste, prenez soin de vérifier les entrées null lorsque cela est approprié, alors cela ne devrait pas être un problème.

4voto

John Kugelman Points 108754

Le problème est celui de la propriété. Vous pouvez utiliser des pointeurs intelligents, par exemple les classes boost::shared_ptr et boost::weak_ptr , pour prolonger la durée de vie de vos observateurs au-delà du point de "désallocation".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X