C++ Design Patterns – Handling a Queue of Network Events

cdesign-patternsmessage-queuenetworking

I'm writing an application that sends and receives messages over the network and pushes them into a std::deque queue. and I'm looking for the appropriate programming pattern to handle all of the different possible types of messages that it could receive.

Each type message will have a completely different set of data. For example:

  • Connection success

  • Disconnecting

  • Another peer connected

    • Peer name
    • Peer address
    • Other peer state
  • Sync object state

    • location delta
    • rotation delta
    • other object state
  • Meta Event A

    • Some data
  • Meta Event B

    • Some data

Etc.

My initial reaction is to create a base class Message and then create subclasses ObjectSyncMessage, MetaEventAMessage, PeerConnectedMessage, etc. because each message requires completely different member variables than the other events.

I get stuck when I go to process my message queue. Everything I've read says to avoid using a switch and to determining which class a message belongs to (see here).

Is there a better way to approach this problem? I don't think that I want the messages themselves to act upon the state of the rest of my application, but rather act as data bags, and let me manage my application state in the class that works through the queue.

Best Answer

While processing the messages in the queue, you probably want to call a processing function that is specific to the most-derived type of the message you are looking at. The one place where that information is naturally available is in the message itself.

A design pattern that comes in useful here is the Visitor pattern. This leads to a design where the specific Message classes know how to process themselves, by calling a function that is specific for that message type on a MessageHandler object.

class Message;
class ObjectSyncMessage;
class PeerConnectedMessage;

class IMessageHandler {
public:
  void HandleGenericMessage(Message*);
  void HandleObjectSyncMessage(ObjectSyncMessage*);
  void HandlePeerConnectedMessage(PeerConnectedMessage*);
};

class Message {
public:
  virtual void Handle(IMessageHandler* handler)
  {
    handler->HandleGenericMessage(this);
  }
  //... other methods
};

class ObjectSyncMessage : public Message {
public:
  // override of base-class function
  virtual void Handle(IMessageHandler* handler)
  {
    handler->HandleObjectSyncMessage(this);
  }
  //... other methods
};

class PeerConnectedMessage : public Message {
public:
  // override of base-class function
  virtual void Handle(IMessageHandler* handler)
  {
    handler->HandlePeerConnectedMessage(this);
  }
  //... other methods
};

// in the queue handling function:
Message* aMessage = // read from queue
aMessage->Handle(aMessageHandler);