This is a generic C++ design question.
I'm writing an application that uses a client/server model. Right now I'm writing the server-side. Many clients already exist (some written by myself, others by third parties). The problem is that these existing clients all use different protocol versions (there have been 2-3 protocol changes over the years).
Since I'm re-writing the server, I thought it would be a great time to design my code such that I can handle many different protocol versions transparently. In all protocol versions, the very first communication from the client contains the protocol version, so for every client connection, the server knows exactly which protocol it needs to talk.
The naive method to do this is to litter the code with statements like this:
if (clientProtocolVersion == 1)
// do something here
else if (clientProtocolVersion == 2)
// do something else here
else if (clientProtocolVersion == 3)
// do a third thing here...
This solution is pretty poor, for the following reasons:
- When I add a new protocol version, I have to find everywhere in the source tree that these if statements are used, and modify them to add the new functionality.
- If a new protocol version comes along, and some parts of the protocol version are the same as another version, I need to modify the if statements so they read
if (clientProtoVersion == 5 || clientProtoVersion == 6)
. - I'm sure there are more reasons why it's bad design, but I can't think of them right now.
What I'm looking for is a way to handle different protocols intelligently, using the features of the C++ langauge. I've thought about using template classes, possibly with the template parameter specifying the protocol version, or maybe a class heirarchy, one class for each different protocol version...
I'm sure this is a very common design pattern, so many many people must have had this problem before.
Edit:
Many of you have suggested an inheritance heirarchy, with the oldest protocol version at the top, like this (please excuse my ASCII art):
IProtocol
^
|
CProtoVersion1
^
|
CProtoVersion2
^
|
CProtoVersion3
... This seems like a sensible thing to do, in terms of resuse. However, what happens when you need to extend the protocol and add fundamentally new message types? If I add virtual methods in IProtocol
, and implement these new methods in CProtocolVersion4
, how are these new methods treated in earlier protocol versions? I guess my options are:
- Make the default implementation a NO_OP (or possibly log a message somewhere).
- Throw an exception, although this seems like a bad idea, even as I'm typing it.
- ... do something else?
Edit2:
Further to the above issues, what happens when a newer protocol message requires more input than an older version? For example:
in protocl version 1, I might have:
ByteArray getFooMessage(string param1, int param2)
And in protocol version 2 I might want:
ByteArray getFooMessage(string param1, int param2, float param3)
The two different protocol versions now have different method signatures, which is fine, except that it forces me to go through all calling code and change all calls with 2 params to 3 params, depending on the protocol version being used, which is what I'm trying to avoid in the first place!
What is the best way of separating protocol version information from the rest of your code, such that the specifics of the current protocol are hidden from you?