Search code examples
c++message-queueencapsulationobserver-patternmediator

Encapsulation & Message Objects


When developing an internal message system such as through the Mediator or Observer pattern, what's the best way to encapsulate the message objects that are passed around?

Consider the following Message Object which tells some service to run a new Job. The message contains the Job that needs to be ran.

#define MESSAGE_JOB = 1
class NewJobMessage: public Message
{
    virtual int getType() { return MESSAGE_JOB; }
    Job* m_Job;
}

Now the message could be handled in the following handleMessage function of the service:

bool JobService::handleMessage( Message* msg )
{
    switch( msg.getType() )
    {
    case MESSAGE_JOB:
        runJob( (dynamic_cast<NewJobMessage*>( msg ))->m_Job );
        return true;
    case default:
        return false;
    }
}

This seems all fine and dandy, but the NewJobMessage has to know about the Job class even though it never uses it in any meaningful way; it's just transporting it.

Is it preferred to do some other way to avoid having the message coupled with the data? I thought about using a void* and casting it, but that seems hacky and might be confusing if someone else were to dissect my code.


Solution

  • This is a textbook case of void * being appropriate. The real downside to void * is run-time type checking, but your getType() method is a big tell that you're already down that rabbit hole.

    To make it unconfusing, just keep the structure simple.

    // No need to inherit from this.
    struct Message {
      int type;
      void * data;
    };
    

    As long as you code in an organized way this shouldn't be confusing, untyped data is the standard approach for message queues.