Modèle d'observateur; savoir * ce * qui a changé?

10

J'ai créé deux classes abstraites Subject et Observer qui définissent une interface de modèle Observer classique. Je dérive d'eux pour implémenter le modèle Observer. Un observateur pourrait ressembler à ceci:

void MyClass::Update(Subject *subject)
{
    if(subject == myService_)
    {
        DoSomething();
    }
    else if(subject == myOtherService_)
    {
        DoSomethingElse();
    }
}

C'est bien et ça me dit qui a changé quelque chose. Cependant, cela ne me dit pas ce qui a changé. Parfois, c'est ok parce que je vais juste interroger le sujet pour les dernières données, mais d'autres fois, j'ai besoin de savoir ce qui a changé exactement sur le sujet. Je remarque qu'en Java, ils ont à la fois une méthode notifyObservers () et une méthode notifyObservers (Object arg) pour probablement spécifier les détails de ce qui a changé.

Dans mon cas, j'ai besoin de savoir si l'une des deux actions différentes s'est produite sur le sujet et, s'il s'agit d'une action particulière, de connaître un nombre entier lié à cette action.

Mes questions sont donc:

  1. quelle est la façon C ++ de passer un argument générique (comme le fait Java)?
  2. Observer est-il même le meilleur modèle? Peut-être une sorte de système d'événements?

MISE À JOUR

J'ai trouvé cet article qui parle de modèle du modèle Observer: Implémentation d'un modèle Subject / Observer avec des modèles . Cela m'a fait me demander si vous pouviez mettre en forme un argument.

J'ai trouvé cette question de débordement de pile qui parle de modeler l'argument: Modèle d'observateur de sujet basé sur un modèle - Dois-je utiliser static_cast ou dynamic_cast . Cependant, le PO semble avoir un problème auquel personne n'a répondu.

L'autre chose que je pourrais faire est de changer la méthode Update pour prendre un objet EventArg comme dans:

void MyClass::Update(Subject *subject, EventArg arg)
{
  ...

Ensuite, créez des sous-classes de EventArg pour des données d'argument spécifiques, puis je suppose que vous les restituez à la sous-classe spécifique dans la méthode de mise à jour.

MISE À JOUR 2

A également trouvé un article, sur la création d'un framework c ++ asynchrone basé sur les messages; la partie 2 qui explique comment le sujet doit communiquer des détails sur ce qui a changé.

J'envisage maintenant sérieusement d'utiliser Boost.Signals . Utiliser mon propre modèle d'observateur était logique quand c'était simple, mais le modèle et le type d'argument commencent à se compliquer. Et j'ai peut-être besoin de la sécurité des threads de Boost.Signals2.

MISE À JOUR 3

J'ai également trouvé quelques articles intéressants sur le modèle d'observateur:

Observateur généralisant par Herb Sutter

Implémentation du modèle d'observateur en C ++ - Partie 1

Expériences de mise en œuvre du modèle de conception d'observateur (partie 2)

Expériences de mise en œuvre du modèle de conception d'observateur (partie 3)

Cependant, j'ai changé mon implémentation pour utiliser Boost.Signals qui, bien que probablement un peu gonflé à mes fins, fonctionne correctement. Et probablement aucune préoccupation de ballonnement ou de vitesse n'est pertinente.

Utilisateur
la source
Les questions dans le corps ne semblent pas vraiment correspondre à votre titre. Quel est vraiment le cœur de la question?
Nicole
@Renesis: J'utilise actuellement le modèle Observer comme dans l'exemple de code au début de mon message. Pour le code sur lequel je travaille actuellement, il s'avère que j'ai besoin de savoir spécifiquement ce qui a changé pour pouvoir réagir en conséquence. Mon implémentation actuelle du modèle d'observateur (qui est le modèle standard) ne fournit pas ces informations. Ma question est de savoir comment obtenir au mieux des informations sur ce qui a changé.
Utilisateur
@Renesis: Voici un fil de discussion qui pose une question similaire à la mienne: gamedev.net/topic/497105-observer-pattern
Utilisateur
Mise à jour de l'annonce2: je soutiens définitivement l'utilisation de Boost.Signals. C'est beaucoup plus pratique que de rouler le vôtre. Il y a aussi libsigc ++ si vous vouliez quelque chose de plus léger juste pour cette tâche (Boost.Signals utilise beaucoup de code du reste de Boost); ce n'est pas thread-safe cependant.
Jan Hudec
En ce qui concerne les questions de vitesse: Je ne sais pas précisément comment Boost.Signals est rapide, mais c'est seulement une préoccupation lorsque vous avez une énorme quantité d'événements qui volent autour ...
Max

Réponses:

4

Que ce soit C ++ ou JAVA, une notification à l'observateur peut accompagner les informations de ce qui a changé. Les mêmes méthodes notifyObservers (Object arg) peuvent également être utilisées en C ++.

Généralement, le problème restera est qu'il pourrait y avoir plusieurs sujets envoyés à un ou plusieurs observateurs et, par conséquent, class argils ne peuvent pas être codés en dur.

Habituellement, la meilleure façon de faire est de créer arg sous la forme de messages / jetons génériques qui forment le même type de données pour différentes classes mais les valeurs diffèrent pour les différentes classes observées. Alternativement, si toutes ces valeurs de notification sont dérivées de la classe sur une classe basée qui est commune à tous.

Pour le modèle d'observateur, il est important que le type de données Arg ne soit pas codé en dur entre l'observé et l'observateur - sinon c'est un couplage qui rend les choses difficiles à évoluer.

MODIFIER
Si vous voulez que cet observateur non seulement observe, mais doit également effectuer beaucoup d'autres tâches en fonction de ce qui a changé , vous pouvez également consulter le modèle de visiteur . Dans le modèle de visiteur, l'observateur / visiteur appelle l' objet modifié et peut donc non seulement savoir ce qu'est la modification, mais peut réellement y travailler

Dipan Mehta
la source
5
Si l'observateur interprète l'argument, il y a un couplage, peu importe comment vous masquez le type. En fait , je dirais qu'il est plus difficile d'évoluer si vous passez Object( void *, boost::anyou quelque chose de la même générique) que si vous passez type spécifique, car avec le type spécifique que vous verrez au moment de la compilation que quelque chose a changé, alors que le type générique , il compilera et cessera de fonctionner, car l'observateur ne pourra pas travailler avec les données réelles transmises.
Jan Hudec
@JanHudec: Je suis d'accord avec cela, mais cela signifie-t-il que vous créez un ensemble spécifique de sous-classes Observateur / Sujet pour chaque argument (c'est-à-dire pour chaque cas d'utilisation)?
Utilisateur
@JanHudec: aussi le couplage n'est qu'une façon. Le sujet n'a aucune idée des observateurs. Oui, l'observateur connaît le sujet, mais n'est-ce pas ainsi que fonctionne le modèle d'observateur?
Utilisateur
1
@User: Oui, je crée une interface spécifique pour chaque sujet et chaque observateur implémente les interfaces des sujets qu'il doit observer. Eh bien, tous les langages que j'utilise ont des pointeurs de méthode liés dans le langage ou le framework (délégués C #, std::functionBoost C ++ 11 boost::function, Gtk + GClosure, méthodes liées à python, etc.), donc je définis simplement des méthodes avec les signatures appropriées et je demande au système de créer le réel observateur. Le couplage n'est en effet qu'une façon, le sujet définit l'interface pour les observateurs, mais n'a aucune idée de leur mise en œuvre.
Jan Hudec
0

Il existe plusieurs façons d'envoyer un argument d'événement générique "Java Like" en C ++.

1) Déclarez l'argument d'événement comme void * et convertissez-le dans la bonne classe dans le gestionnaire d'événements.

2) Déclarez l'argument d'événement en tant que pointeur / référence à une nouvelle classe / interface telle que (en réponse à votre exemple)

class GenericEventArgument
{
  virtual bool didAction1Happen() = 0;
  virtual int getActionInteger() = 0;
};

Et que les classes d'arguments d'événement réels dérivent de cette classe.

Quant à votre question concernant un modèle basé sur l'observateur, consultez

http://www.codeproject.com/Articles/3267/Implementing-a-Subject-Observer-pattern-with-templ

Gonen I
la source
-1

Je ne sais pas si c'est le nom canonique, mais depuis mes vieux jours Smalltalk je me souviens du terme "aspect" pour identifier ce qui a changé sur l'observable.

Ce n'est pas aussi complexe que votre idée d'EventArg (et de le sous-classer); il passe juste une chaîne (pourrait même être une constante entière) de l'observable à l'observateur.

Le plus: il n'y a que deux méthodes simples ( update(observable)etupdateAspect(observable, aspect)

Moins: L'observateur devra peut-être demander à l'observable des informations supplémentaires (c'est-à-dire votre "nombre entier")

virtualnobi
la source