Assume I have a factory which takes in a series of bytes and outputs a pointer to a newly-created abstract message.
Now, I would like to define some extensible, manageable, and clean way to "handle" those various messages without knowing the actual concrete type. To me, this sounded like a great application for the visitor design pattern. My various handler objects (i.e., visitors) would provide implementations of abstract methods for the messages they actually "care about" (i.e., handle).
However, in my application, the majority of messages can be segmented into distinct groups where each visitor only handles one of those groups. There are a few messages handled by all (or more than one) visitor, but this is the exception.
For example, assume I have Visitor1, Visitor2, and Visitor3. Below is a list of the messages "handled" by each visitor:
- Visitor1
- MessageA
- MessageB
- MessageC
- Visitor2
- MessageA
- MessageD
- MessageE
- Visitor3
- MessageA
- MessageF
- MessageG
Of course, there could be multiple instances of the same type of visitor on the system.
Which could be implemented in C++ as follows:
class MessageA;
class MessageB;
class MessageC;
class MessageD;
class MessageE;
class MessageF;
class MessageG;
class AbstractVisitor {
public:
virtual bool visit(const MessageA& msg) { return false; }
virtual bool visit(const MessageB& msg) { return false; }
virtual bool visit(const MessageC& msg) { return false; }
virtual bool visit(const MessageD& msg) { return false; }
virtual bool visit(const MessageE& msg) { return false; }
virtual bool visit(const MessageF& msg) { return false; }
virtual bool visit(const MessageG& msg) { return false; }
};
class Visitor1 : public AbstractVisitor {
public:
bool visit(const MessageA& msg) override { return true; }
bool visit(const MessageB& msg) override { return true; }
bool visit(const MessageC& msg) override { return true; }
};
class Visitor2 : public AbstractVisitor {
public:
bool visit(const MessageA& msg) override { return true; }
bool visit(const MessageD& msg) override { return true; }
bool visit(const MessageE& msg) override { return true; }
};
class Visitor3 : public AbstractVisitor {
public:
bool visit(const MessageA& msg) override { return true; }
bool visit(const MessageF& msg) override { return true; }
bool visit(const MessageG& msg) override { return true; }
};
class AbstractMessage {
public:
virtual bool accept(AbstractVisitor& v) = 0
};
class MessageA : public AbstractMessage {
public:
bool accept(AbstractVisitor& v) { v.visit(*this); }
};
//...
The maintenance of this code isn't too bad since it only requires the modification of the AbstractVisitor
interface each time a new message is added. However, it still seems a bit problematic to have to keep a single file with one big list of all the possible messages.
The second problem I see is that each visitor has a large vtable containing all the possible visit()
methods for every type of message even when each particular visitor only uses a few of them.
The third problem is really a result of the other two problems in that, for example, MessageB
and MessageF
are really not related except for the common interface. They represent two completely different operations ALWAYS handled by two completely different endpoints.
Since I'm sure this type of problem occurs relatively often. My question is, is this (generally) the most optimal design in this case? Or is there a better pattern for something like this? Are large vtables really a concern (as far as I know, that's one pointer per virtual method times the number of concrete implementations)?