How to Elegantly convert switch+enum with polymorphism
I'm trying to replace simple enums with type classes.. that is, one class derived from a 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 which all seem less elegant even then the basic switch statement. Using dyanimc_cast, inheriting from a messageHandler class that needs to be updated every time a new message is added, using a container of function pointers, all seem to defeat the purpose of making code easier to maintain by replacing switches with polymorphism.
This is as close as I can get: (I use templates to avoid inheriting from an all-knowing handler interface)
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;
开发者_JAVA技巧 alpha->apply(this);
bravo->apply(this);
delete alpha;
delete bravo;
}
void applyAlpha() {
// cout << "Applying alpha\n";
}
void applyBravo() {
// cout << "Applying bravo\n";
}
};
Obviously, this doesn't compile but I'm hoping it gets my problem accross.
Well, after giving in to dynamic_cast 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;
}
It looks like you are trying to find some sort of double-dispatch system. Look into the Visitor pattern or other multiple-dispatch systems.
Your Bravo and Alpha classes are actually closures... Too bad C++ does not support them directly.
You could use a member pointer to do 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 may not be exact, but you know hat I mean)
I don't necessarily have an answer for your design pattern issue (though Modern C++ Design has a lot to say about it), but I do want to address your switch vs inheritance comment.
The problem with that simple swtich statement is maintainability. If that switch statement were in 1 location, then it's probably about the same amount of typing to create classes and inherit, but that switch statement is still a ticking time-bomb awaiting yet another state added without adding a case for it. If you assert the default:, you'll catch it at run time - eventually, but that's very poor. If you setup a bunch of function pointers and compile time assert on the table's size, you're doing better, but that's another level deeper than the switch statement. And this all goes out the window as soon as you have a second place in the code that needs to check state.
It's just that much easier once you have your interface class setup to let the compiler handle all the junk code of switching on states internally. You add the class need not worry about any other code as long as you follow the interface.
精彩评论