Atul got me totally hooked with his idea to use the visitor design pattern with Packets ;-). By using a visitor, we will get rid of the error prone switch statements; we’ll have multiple smaller functions instead of a single big one; we won’t need to use castFrom() or dynamic_cast; and we’ll have clearer code!
That all sounds great it comes for a price: each derived class must implement a further (actually quite trivial) virtual function and we should derive our concrete visitors from an abstract base class. This means that each time we add a new class in the Packet hierarchy we would need to add a function to the abstract visitor class and to ALL concrete visitor classes. Ok, adding a new packet will not happen so often, but still the solution is less flexible. We would also need a big number of concrete visitors for each different context. In most of the cases we’ll need a visitor to simply delegate responsibility to another object (like in PacketProcessor). Another drawback of visitors is that they need to know the complete class hierarchy of what they are visiting and that would be a problem if we have plugins providing some additional classes.
The sample project I provide here tries to address these shortcomings by providing a kind of “dynamic” visitor. No additional virtual functions would be required and no multiple concrete visitors would be necessary. The idea of visitor is to do double dispatching for languages that do not have build-in support for that. C++ is one of these languages. Hopefully, we use the Qt framework which provides a metaclass system for classes derived from QObject and we can use that!
The idea is to dispatch an object to a functionm which should handle the exact dynamic type of the object. For example, if we receive a Packet object from a PacketSession, then if would be nice to have the correct overload, e.g. processPacket (Pong *) or processPacket (Ping *) called with the object as an argument. We could go a step further and have the corresponding functions from different objects called depending on the dynamic type of the object!
Perfectly we would be able to write code like this:
PacketProcessor::PacketProcessor()
{
...
objectDispatcher.addTarget (pongCache, “processPong”, DYSPATCH_OBJECT (Pong));
objectDispatcher.addTarget (dynamicSearcher, “processQueryHits”, DISPATCH_OBJECT (QueryHits));
...
}
...
PacketProcessor::packetReceived (Packet *packet)
{
objectDisaptcher.dispatch (packet);
}
We could even go a step further and make dispatch() a slot and connect the signals to it directly!
The attached sample is not yet as advanced as described above. It just shows the above might work :-)!
Now more details about the sample. It relies on typeid() to find the dynamic type of the object and to find the correct mapping to a QObject target and the function of the target that should get called. To call the correct method of the correct target at runtime I use QMetaObject::invokeMethod(). This call is actually very slow, but after I took a look in qmetaobject.cpp and qobjectdefs.h I think I’ll be able to optimize it a lot. I think about resolving the ID of the method, which has to be invoked, in the call to ObjectDispatcher::addTarget(). The implementation of ObjectDispatcher::dispatch() will use this id and call qt_metcall() directly. If that works, then dispatch() should work faster than signals and slots do!
I’d like to know what you guys think about that! I’m open to suggestions and hope you’ll have some interesting ideas for improvement of the concepts or the implementation.
If anybody is interested in understanding how the sample works but has trouble understanding everything from the source, I’m ready to give some further infos.
It’ll be great if some of you collaborate! Two is more than one, and two as a whole is more than two separate ones ;-).
Regards,
Peter
