Need to pass data with notification in the observer design pattern

I am implementing a system using the observer pattern to decouple my data generating/modifying code from the data handling code. As there are different notifications my notify method takes a 'message' parameter; if I make this a struct

I need to pass some data with the notification - there are several different types of change that can occur, so my
There is additional data that I feel should be provided with the call to notify(), in particular the updated data.

Looking at the code below, does anyone have any feeling on the alternatives I propose, or can suggest something different? I'm not sure I've explained it supremely well, let me know if I can clarify.

TIA,
--rob
struct Message
{
   virtual ~Message(void) {}
};
 
 
// methods not referred to are omitted
struct IMessageObserver
{
   virtual void notify(const Message& msg) = 0;
   virtual void notify2(const Message& msg, void* data) = 0;
};
 
 
struct MessageWithoutData
   : public IMessage
{
} MSG_NO_DATA; // no members and this instance mean I can compare (msg == MSG_NO_DATA) to determine what notification I've been given
 
 
struct MessageWithData
   : public IMessage
{
   MessageWithData(int newValue)
      : value(newValue)
   {}
 
   int value;
} // can't do a straightforward comparison in notify() now - RTTI instead?
 
 
// the example notifications
void doANotification(IMessageObserver& observer)
{
   int newValue(24);
 
   // should I construct with the data?
   observer.notify(MessageWithData(newValue));
 
   // or pass the data separately
   observer.notify2(MSG_NO_DATA, &newValue);
}

Open in new window

LVL 6
boycyAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Infinity08Commented:
You can put all the message logic inside the Message classes (using virtual methods), so the notified code doesn't have to know the exact type - it can just call a virtual method, and the right action will occur.

Or, you can place some type information inside the Message classes.

Or you can use overloaded notify methods that each treat a different kind of message.

Or ...
0
boycyAuthor Commented:
Hi Infinity,

> You can put all the message logic inside the Message classes (using virtual methods), so the notified
> code doesn't have to know the exact type - it can just call a virtual method, and the right action will occur.

This was my initial thinking, as it actually uses the available polymorphism.
I shied away from it though - I have a set of messages and observers, but surely it's each observer's responsibility to know what to do in response to MessageX. If MessageX contains the knowledge of what to do to each observer then loose coupling introduced by the pattern is lost. Is there another way?

> Or you can use overloaded notify methods that each treat a different kind of message.
So an observer might overload, say:
   IObserver::notify(Message& msg) {}
with:
   ConcreteObserver::notify(FooChanged& msg);

But when I call
   pObserver->notify(FooChanged(24));
the generic base class implementation will be called. Is there some other way of coding this to do what (I think) I want to do?

Many thanks,
--rob
0
itsmeandnobodyelseCommented:
You could both make the observer class and the message class a virtual class. Then the derived observer classes can have their own virtual implementations of the notify function and the sender of messages can send derived messages (with additional members). Of course the (derived) observer class must know what messages to handle and most probably would need a dynamic_cast to get the specialized message.
0
Cloud Class® Course: Amazon Web Services - Basic

Are you thinking about creating an Amazon Web Services account for your business? Not sure where to start? In this course you’ll get an overview of the history of AWS and take a tour of their user interface.

Infinity08Commented:
>> If MessageX contains the knowledge of what to do to each observer then loose coupling introduced by the pattern is lost.

True, but it all depends on the level of abstraction you can put on the messages. As well as the kind of processing that needs to be done on a notify.

Taking the simple example of a notify handler that simply has to show the message to the user, this could be easily implemented with a virtual getMsg method on the Message object, which returns the message as a string, which can then be shown to the user by the notify method. See the simple code example below.

If what you need to do becomes more complicated, this will indeed be less "pretty".
class Message {
  public :
    virtual std::string getMsg() = 0;
};
 
class MessageTypeA : public Message {
  private :
    std::string msg;
 
  public :
    MessageTypeA(const std::string &m) : msg(m) { }
    virtual std::string getMsg() {
      return msg;
    }
};
 
class MessageTypeB : public Message {
  private :
    std::string error;
    int id;
 
  public :
    MessageTypeA(const std::string &e, int i) : error(e), id(i) { }
    virtual std::string getMsg() {
      std::stringstream ss;
      ss << "ERROR (" << id << ") : " << error;
      return ss.str();
    }
};
 
 
// the notify handler becomes very simple, and does not need to know the exact type of the message
void notify(Message &message) {
  std::cout << message.getMsg() << std::endl;
}

Open in new window

0
Infinity08Commented:
>> > Or you can use overloaded notify methods that each treat a different kind of message.

Something like :
#include <iostream>
#include <string>
 
class Message {
  public :
    virtual std::string getMsg() = 0;
};
 
class MessageTypeA : public Message {
  public :
    virtual std::string getMsg() {
      return "Message Type A";
    }
};
 
class MessageTypeB : public Message {
  public :
    virtual std::string getMsg() {
      return "Message Type B";
    }
};
 
void notify(Message &message) {
  std::cout << "Message received : " << message.getMsg() << std::endl;
}
 
void notify(MessageTypeA &message) {
  std::cout << "Message Type A received : " << message.getMsg() << std::endl;
}
 
void notify(MessageTypeB &message) {
  std::cout << "Message Type B received : " << message.getMsg() << std::endl;
}
 
int main(void) {
  MessageTypeA msgA;
  MessageTypeB msgB;
  notify(msgA);
  notify(msgB);
  return 0;
}

Open in new window

0
boycyAuthor Commented:
@itsme

> You could both make the observer class and the message class a virtual class.
I don't understand - do you using IMessage or IMessageObserver as virtual bases?


> Or you can use overloaded notify methods that each treat a different kind of message.
> Something like : ...
I was struggling to get that to work in a class hierarchy. I defined virtual notify() for IObserver, but then even if I overrode it in the concrete observers with more specific ones, when calling notify() on an observer through an IObserver reference/pointer, IObserver::notify(IMessage&) was always being called.

I have discovered that I need to override that method in the derived class even if I don't use it, but I'm not really understanding what's going on with the vtable in this circumstance. 2 example classes below - one which doesn't work, one which does.
If someone is able to explain what actually happens when I call IObserver::notify(IMessage& msg) for a particular observer that would be great!

--rob
struct IObserver
{
  virtual void notify(IMessage &msg) {}
};
 
struct FailingObserver : public IObserver
{
  virtual void notify(FooMessage& msg) {}
};
 
struct WorkingObserver : public IObserver
{
  virtual void notify(IMessage& msg)   {}
  virtual void notify(FooMessage& msg) {}
};
 
FailingObserver fo;
WorkingObserver wo;
IObserver& fio = fo;
IObserver& wio = wo;
 
// this calls IObserver::notify(IMessage&) instead of FailingObserver::notify(FooMessage&) - why?
fio.notify(FooMessage());
 
// this calls WorkingObserver::notify(FooMessage&) now - why?
wio.notify(FooMessage());

Open in new window

0
boycyAuthor Commented:
Sorry, second part of that message was for Infinity, darn fingers...
0
Infinity08Commented:
>> defined virtual notify() for IObserver, but then even if I overrode it in the concrete observers with more specific ones, when calling notify() on an observer through an IObserver reference/pointer, IObserver::notify(IMessage&) was always being called.

Don't make the notify's virtual ;) And don't place them in separate Observers. Just use one big Observer class, that has all overloaded notify methods.

The problem with doing it your way, is that :

(a) you need to know the type of the message when choosing the observer ... so you again need some kind of RTTI (either provided by the system or implemented by yourself)

(b) void notify(IMessage& msg) and void notify(FooMessage& msg) are different overloaded versions of the same method.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
boycyAuthor Commented:
> Just use one big Observer class, that has all overloaded notify methods.
:-O
That sounds horrible - each observer has an area of responsibility that may be affected by particular messages (and may not by others). An observer decides which messages it wants to act upon, and implements the desired functionality for each message it uses.

> (a) you need to know the type of the message when choosing the observer ... so you again need some
> kind of RTTI (either provided by the system or implemented by yourself)
Not here - in mine, each observer is registered with message generator(s) it is interested in. Each one may generate several different update messages.
When a message generator generates a message, it iterates through a vector<IObserver*> and calls notify(IMessage&) on each one.
Thus the message generators only need to know about a collection of abstract observers, and all observers get all messages then choose to process only those messages they are interested in.

> (b) void notify(IMessage& msg) and void notify(FooMessage& msg) are different overloaded versions
> of the same method.
Is that a problem?

--rob
0
Infinity08Commented:
>> An observer decides which messages it wants to act upon, and implements the desired functionality for each message it uses.

Meaning that you'll deal with multiple observers ... ok ... In that case, you can use the virtual mechanism, but you'll still have to implement the needed overloads.


>> and calls notify(IMessage&) on each one.

The base class IMessage ? Then the overloads like I showed won't work obviously.


>> Is that a problem?

It is if you want to use polymorphism to decide which one to choose, because that won't work.


Your requirements seem to be "slightly" different than what I originally understood.

So, you want different kinds of messages and different kinds of observers, where each observer is able to process each kind of message transparently (it could ignore the message).

If you don't want to put message processing logic in the Message objects, this is hard without RTTI. The Observer class simply has no way of distinguishing between the different kinds of messages otherwise (you cannot use the overloads as I suggested earlier, since they require non-polymorphic messages).

There is a nice solution however. It's called multiple dispatch, and here's a description of how it works :

        http://www.eptacom.net/pubblicazioni/pub_eng/mdisp.html

This is not a simple design pattern, and I'd first make sure that none of the other alternatives work better. But it's an idea :)
0
itsmeandnobodyelseCommented:
>>>> I don't understand - do you using IMessage or IMessageObserver as virtual bases?

I would consider to make them both a base class for virtual use.

>>>> IObserver::notify(IMessage&) was always being called
That looks like if the pointer (or reference) passed was pointing to a IObserver object. It must point to a class object where the class was derived from IObserver.

>>>> Don't make the notify's virtual ;)
I object. The objects of different observer classes could be stored in some kind of dictionary which was a static member of the IObserver base class. Each class derived from IObserver makes a one-time 'registration' of a singleton observer object by adding it to the dictionary maintained in the IObserver baseclass. That way the right observer could be chosen by passing a key or by associating a key to the message as a member. The pattern might get enhanced by that, but it gives a lot more variability and avoids a big observer notify function:

typdef std::string ObserverKey;

class IObserver
{
    static std::map<ObserverKey, IObserver*> observers;
public:
    static bool register(const std::string & key, Observer* pobserver)
    {
          observers[key] = pobserver;
          return pobserver != NULL;
    }
    // rest of class
    ....
};

//observer.cpp

...
// somewhere below includes
std::map<ObserverKey, IObserver*> IObserver::observers;


// fooobserver.h
...
class FooObserver : public IObserver
{
     static bool registered;
     // rest of class
     ....

};

// fooobserver.cpp

// somewhere below includes
 bool FooObserver::registered = IObserver::register("Foo", new FooObserver());

That way any observer was registered by a unique name, and when calling notify you could choose the correct observer if you know the key (which maybe could be supplied with the message).
0
ambienceCommented:
How about that? I was too lazy to post it yesterday but I guess its still not outdated.




// Define message types
 
struct Message
{
   virtual ~Message(void) {}
};
 
struct FooMessage : public Message
{
public:
    int getCode() const { return 0; }
};
 
struct BarMessage : public Message
{
public:
    Foo getFoo() const { return Foo(); }
};
 
 
// One change here, reference changed to pointer
struct IMessageObserver
{
   virtual void notify(const Message* msg) = 0;
};
 
 
// Using Observers: Variation 1
 
struct FooObserver : public IObserver
{
	virtual void notify(const Message* msg) 
	{
		FooMessage* myMsg = dynamic_cast<FooMessage*>(msg);
		if(myMsg == NULL) panic("Not my msg!");
		.... 
	}
};
 
// Using Observers: Variation 2
 
template <class MsgType>
class ObserverAdapter
{
private:
	typedef typename MsgType MessageType;
	virtual void notify(const Message* msg) 
	{
		MessageType* myMsg = dynamic_cast<MessageType*>(msg);
		if(myMsg == NULL)
			throw BadMessageTypeException();
		
		// delegate to the specialized version
		notify(*myMsg);
	}
 
protected:
	virtual void notify(const MessageType& msg) = 0;
}
 
 
struct BarObserver : public ObserverAdapter<BarMessage>
{
	virtual void notify(const BarMessage& msg)   
	{
		....
	}
};

Open in new window

0
ambienceCommented:
Oops!
class
 
ObserverAdapter 

should be


class
 
ObserverAdapter : public IMessageObserver

same for FooObserver


struct
 
FooObserver
 
:
 
public 
IMessageObserver

Open in new window

0
boycyAuthor Commented:
@Infinity:

>> and calls notify(IMessage&) on each one.
> The base class IMessage ? Then the overloads like I showed won't work obviously.

Although if I implement:
   IObserver::notify(IMessage&) // method 1
   ConcreteObserver::notify(IMessage&) // method 2
   ConcreteObserver::notify(ConcreteMessage&) // method 3
then do e.g.:
   IObserver& observer = returnsAnObserver();
   IMessage& message = returnsAConcreteMessage();
   observer.notify(message);

even though I'm using a reference-to-base-class for both observer and message types, it will call method 3 - but only if I've defined method 2. I don't understand why this is, so if someone can explain what in the virtual table makes the difference here that would be great!

>> Is that a problem?
> It is if you want to use polymorphism to decide which one to choose, because that won't work.
But it does work! As long as notify is redefined for the generic IMessage& in the derived observer class.

> If you don't want to put message processing logic in the Message objects, this is hard without RTTI.
Ok, if RTTI is the simplest way to go I'm happy with that :)

> There is a nice solution however. It's called multiple dispatch ...
Yikes! Not something I'd ever heard of before. It would be OTT for what I need, but an interesting way to do things.


If I can understand the C++ internals causing the overloads to work or not work as described above then I'll be happy with that solution. If not, then I'm happy to go with RTTI in each notify() method.
0
Infinity08Commented:
It should call method 2, since you're passing an IMessage&, not a ConcreteMessage&. It doesn't matter that the reference actually refers to a ConcreteMessage, the code does not know that.
0
Infinity08Commented:
Posted an incomplete reply - here's the right one :

>> it will call method 3 - but only if I've defined method 2.

It should call method 2, since you're passing an IMessage&, not a ConcreteMessage&. It doesn't matter that the reference actually refers to a ConcreteMessage, the code does not know that.


>> But it does work!

Can you show the exact code you use, because maybe I'm misunderstanding how you do things.


>> It would be OTT for what I need, but an interesting way to do things.

I've used it with success once :) It is great fun lol.
0
boycyAuthor Commented:
Hi Infinity,

Must apologies - I think whilst trying out some things I must've gotten my knickers in a twist, I can't reproduce the behaviour I was talking about - as you stated, calling *pObserver.notify(*pMessage) will always result in notify(Message&) being called - either in the base or derived.
Not really sure how I got it running any different - certainly this behaviour makes much more sense!

So I will implement my solution with RTTI :-)
Thanks for your help -- also to itsmeandnobodyelse and ambience for your contributions :-)

--rob
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Programming Theory

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.