Currently I'm writing the message handling system for my game server. I'm using double-dispatch design pattern to pick the needed handler according to message id, which retrieved from a data buffer. But I ran into a following problem: I need to determine the derived class of the message to pass it into a double-dispatch handler object. the only solution I found is to write a factory method in base class with the switch inside, that will create needed derived class according to message id.
class Message
{
public:
void dispatch(Handler& handler)
{
dispatchImpl(handler);
}
protected:
virtual void dispatchImpl(Handler& handler) = 0;
};
template <typename TDerived>
class BaseMessage : public Message
{
public:
BaseMessage(unsigned short messageId)
: id(messageId) {};
virtual ~BaseMessage() = default;
unsigned short getId() const { return id; }
static std::unique_ptr<Message> create(BitStream& data)
{
switch (data.ReadShort())
{
case 1: return std::make_unique<FirstMessage>();
case 2: return std::make_unique<SecondMessage>();
case 3: return std::make_unique<ThirdMessage>();
// ...
}
}
protected:
unsigned short id;
virtual void dispatchImpl(Handler& handler) override
{
handler.handle(static_cast<TDerived&>(*this));
}
};
How can I improve my design to avoid big-switch statement? Thanks in advance.
You might use a std::map<short,std::function<std::unique_ptr<Message> (BitStream& data)>
and allow registration of those factory functions dynamically:
std::map<short,std::function<std::unique_ptr<Message> (BitStream& data)> createMessageHandlers;
void registerCreateMessageHandler(short id,std::function<std::unique_ptr<Message> (BitStream& data)> fn) {
createMessageHandlers[id] = fn;
}
and use it like
registerCreateMessageHandler(1, [](BitStream& data) { return std::make_unique<FirstMessage>();});
registerCreateMessageHandler(2, [](BitStream& data) { return std::make_unique<SecondMessage>();});
Your message type constructors should probably take the BitStream&
as a constructor parameter.
You should consider to use a tool like google protocol buffers to define your communication protocols.
That would help a lot in properly versioning, and parsing message packets (endianess neutral) over the wire (or wireless).
In combination with e.g. boost::asio or zeromq for the transport handling, that should give you the maximum flexibility and proper mechanisms to separate the transport and semantical layers of communication.