How to elegantly convert switch + enum with polymorphism
I am trying to replace simple enums with class types i.e. one class derived from the base for each type. So, for example, instead of:
enum E_BASE { EB_ALPHA, EB_BRAVO };
E_BASE message = someMessage();
switch (message)
{
case EB_ALPHA: applyAlpha();
case EB_BRAVO: applyBravo();
}
I want to do this:
Base* message = someMessage();
message->apply(this); // use polymorphism to determine what function to call.
I have seen many ways to do this that seem less elegant, even then with basic switch statements. By using dyanimc_cast, inheriting from the messageHandler class, which needs to be updated every time a new message is added, using the function pointer container, everyone seems to be aiming to make the code easier to run by replacing switches with polymorphism.
This is as close as possible: (I'm using templates to avoid inheriting from the omniscient API)
class Base
{
public:
template<typename T> virtual void apply(T* sandbox) = 0;
};
class Alpha : public Base
{
public:
template<typename T> virtual void apply(T* sandbox)
{
sandbox->applyAlpha();
}
};
class Bravo : public Base
{
public:
template<typename T> virtual void apply(T* sandbox)
{
sandbox->applyBravo();
}
};
class Sandbox
{
public:
void run()
{
Base* alpha = new Alpha;
Base* bravo = new Bravo;
alpha->apply(this);
bravo->apply(this);
delete alpha;
delete bravo;
}
void applyAlpha() {
// cout << "Applying alpha\n";
}
void applyBravo() {
// cout << "Applying bravo\n";
}
};
Obviously this won't compile, but I hope this will cause my problem.
a source to share
Well, after providing dynamic_multiple and multiple inheritance, I came up with this thanks to Anthony Williams and jogear.net
class HandlerBase
{
public:
virtual ~HandlerBase() {}
};
template<typename T> class Handler : public virtual HandlerBase
{
public:
virtual void process(const T&)=0;
};
class MessageBase
{
public:
virtual void dispatch(HandlerBase* handler) = 0;
template<typename MessageType>
void dynamicDispatch(HandlerBase* handler, MessageType* self)
{
dynamic_cast<Handler<MessageType>&>(*handler).process(*self);
}
};
template<typename MessageType> class Message : public MessageBase
{
virtual void dispatch(HandlerBase* handler)
{
dynamicDispatch(handler, static_cast<MessageType*>(this));
}
};
class AlphaMessage : public Message<AlphaMessage>
{
};
class BravoMessage : public Message<BravoMessage>
{
};
class Sandbox : public Handler<AlphaMessage>, public Handler<BravoMessage>
{
public:
void run()
{
MessageBase* alpha = new AlphaMessage;
MessageBase* bravo = new BravoMessage;
alpha->dispatch(this);
bravo->dispatch(this);
delete alpha;
delete bravo;
}
virtual void process(const AlphaMessage&) {
// cout << "Applying alpha\n";
}
virtual void process(const BravoMessage&) {
// cout << "Applying bravo\n";
}
};
int main()
{
Sandbox().run();
return 0;
}
a source to share
Your Bravo and Alpha classes are actually closed ... Too bad C ++ doesn't support them directly.
A member pointer can be used for this:
typedef void (Sandbox::*SandboxMethod)();
struct BrAlpha {
BrAlpha(SandboxMethod method) : method(method){}
void apply(Sandbox sb){sb->*method();}
};
BrAlpha alpha(&Sandbox::applyAlpha);
BrAlpha bravo(&Sandbox::applyBravo);
(syntax might not be accurate, but you know what I mean)
a source to share
I'm not necessarily responsible for the design pattern issue (although Modern C ++ Design has a lot to say about this), but I want to refer to your switch vs. comment.
The problem with this simple swtich expression is maintainability. If this switch statement was in 1 place, then probably about the same amount of input for class creation and inheritance, but this switch statement is still a ticking time bomb waiting to add another state without adding a case to it. If you declare a default: you will catch it at runtime - eventually, but it's very bad. If you've set up a bunch of function pointers and compile time in table size, you're doing better, but at a different level deeper than a switch statement. And this all comes out of the window as soon as you have a second place in the code that should check the state.
It is much easier if you have an interface class set up to allow the compiler to handle any unwanted state enable codes internally. You are adding a class that shouldn't bother with any other code if you follow the interface.
a source to share