I'm tasked with implementing a messaging system for a real-time simulation. There are several different kinds of messaging objects that need to exist in this system, and more might be added in the future. These objects represent the primary means of communication between the players in the sim. Assuming I fully understand my requirements, the messaging objects can be defined by the following attributes:
The simulation code currently only supports one send and receive protocol and one message format. My job is to make the code more extensible so it can support future changes/additions to protocols and message structure. A first-cut approach to this would be to define abstract base classes for each of the attributes and have each messaging object be composed of handles to them. Every messaging object can then be written as different combinations of protocol and format objects. My concern is that this design can quickly become over-generalized and thus a maintenance nightmare. I cringe at the thought of sifting through a dozen files just to figure out how the heck a
FooMessaging really works.
I'll be writing this in C++, but I think this is really more a general design question. Are there any "standard" patterns or best practices I can apply here?
Okay, I suspect you're doing an A to Z process starting at about G.
First of all, what are your use cases? What are you trying to do? What kind of "messaging system" are you building? EMAIL? IM? Telepathic?
Second, what is the domain that you get from those use cases?
Now, having thought that through, then yes, you often find it convenient to make an ABC for the basic classes. Consider making an interface instead (although the distinction between interfaces and ABCs in C++ is less well defined than other languages.) Over the last 20 years, inheritance-based OO has turned out to impose a lot of problems, so that interfaces and aggregation are now favored. Not always better, but you should think of them first.
Now, tell me what the physical thing corresponding to the "protocol" is? Do you mean to model something like a message stream? Is it a communications medium?
Format in particular sounds suspicious, if not immediately wrong: message formats tend to be tied intimately to the protocol.
Basically, back up a bit and tell us more about what you're trying to do, first.
Aha. Okay, see, we get a lot of help from this. First, you mean "protocol" in the sense of the available operations — a perfectly reasonable usage, but confusing when you confound it with TCP vs UDP, that sort of thing, as I did.
Now, that said, you do indeed have a couple of options at least. In the following, I use the term "Player" for any entity in the sim that can send or receive messages.
The key notion here is to design around the loci for change, and around nonfunctional requirements. The obvious loci of change are
you can have several "protocols" in your sense, and if you have as many as three, you should just plan that there can always be more. If you expect more than two, plan that you can have arbitrarily many.
you can have several different ways of formatting messages, ie, JSON, XML, or YAML, say. AGain, if you expect more than two, plan that you can have arbitrarily many.
You can have several transport mechanisms. From the sounds of things, you can at least use UNIX-domain sockets×shared memory×named pipes, but then my intuition says you also have the option of local and remote, which means you also have the option of UDP or TCP. Definitely more than two, plan for extensibility.
Use a Proxy pattern, where each protocol defines an interface that must be implemented in one of several ways, depending upon the messaging format underneath. Players advertise what messages they're willing to receive; at the time they are prepared to receive a message, they construct something that invokes their code for any specific operation message they receive. To send, they construct an object implementing the appropriate interface for their receiving partner Player.
Under the covers, these objects can have several realizations, each for a different messaging format and transport mechanism.
Examples of a system like this include SOM/DSOM a/k/a CORBA, and Java RMI using the Remote interface.Option 2:
You can build something around the Command pattern. Each protocol has a specific implementation of "send me", and your receiver simply knows how to reconstruct an object sent. The command objects have a mix-in (multiply-inherited, or passed as an object at run time) that knows how to marshall and serialize the Command object; you can have a second one that understands the transport mechanism.
You send a message on a particular protocol by serializing the object and using your transport mechanism to move the serialized form to the receiver. The receiver "rehydrates" the object, and invokes the appropriate methods based on the actual type sent.
An example of this is Java RMI with the Serializable interface instead of using Remote.