0

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.

jakedipity
  • 890
  • 8
  • 19
  • Missing semicolon after `return` statement: `virtual int getType() { return MESSAGE_JOB; }` – Azeem Jul 22 '17 at 05:07

2 Answers2

0

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.

QuestionC
  • 10,006
  • 4
  • 26
  • 44
  • I thought the `getType()` was a bit superfluous Now in the event that I have a message that needs to send two pointers should I inherit from this structure? Or be doubly indirect and have the `void * data` point at a structure that contains the two pointers – jakedipity Jul 22 '17 at 23:41
  • Yes, you would have `void * data` point to a structure containing two pointers. If that case bothers you, you can add another pointer structure to `struct Message`. Really you can add anything you want to it (the windows MSG structure contains a timestamp, x/y of the cursor, an identifier for the window sending the message and two general purpose pointers/ints). Invariably there's a tradeoff because some messages will not need the extra fields and others will need a level of indirection to support extra fields. – QuestionC Jul 24 '17 at 01:13
  • `getType()` isn't superfluous. A message queue message fundamentally needs to contain its data and some indicator of how to handle that data. The type information expresses how you handle the data. The `void *` expresses the data. The message shouldn't *contain* a job that needs to be run. The message *represents* a job that needs to be run. Passing a message in a message queue is analagous to one thread calling a function in a second thread. The type is the function name. The data is the function arguments. – QuestionC Jul 24 '17 at 01:18
  • I have a component responsible for running jobs so the data of the message would be the _job_. Thanks. Your answers shed a lot of light on the best approach this. – jakedipity Jul 24 '17 at 10:51
  • Looking at the windows messaging API might help as an example. I find this page helpful to refer to though it depends on some windows API knowhow. https://msdn.microsoft.com/en-us/library/windows/desktop/ff381405(v=vs.85).aspx – QuestionC Jul 24 '17 at 22:57
0

You did not mention how these messages are being passed around or how far they may go (across a net?) but in general, I would advise STRONGLY against passing native objects. It is a recipe for pain and suffering down the road. It's a maintenance and debugging nightmare, a security attack vector, design coupling problem, and it only gets worse when this system gets extended later (because it always does).

So there are a few ways to tackle this:

  • Encode the information using CDR or some such way to encode the job info. The receiver reverses the CDR to get the info back, even if it's not the same language, system, or byte size/order. It's a little tedious to code but not expensive computationally or space-wise.
  • Marshall the request to something higher level like JSON or XML depending on your preferences and experience and the types of info in the data. This is usually fairly easy with third party library assistance but converting to/from text and transmitting it is a bit more expensive. It's also far easier to diagnose problems and investigate things in transit.

All else being equal, I'd lean toward JSON unless the rate at which you need to process and transfer these requests is so high your processing can't keep up. It should be fairly easy with the assistance of modern C++ libraries to handle JSON.

Steve Huston
  • 1,432
  • 13
  • 20
  • This all handled internally in the same process. I simply want to decouple various components of my system and what them to communicate through these message commands. Serialization isn't quite what I want here, but I can think of other places where I'll need it; thanks! – jakedipity Jul 22 '17 at 23:37