如何使用多态优雅地转换switch + enum

时间:2010-05-04 17:20:08

标签: c++ refactoring polymorphism

我正在尝试用类型类替换简单的枚举。也就是说,每个类型的基类派生一个类。例如,而不是:

enum E_BASE { EB_ALPHA, EB_BRAVO };
E_BASE message = someMessage();
switch (message) 
{
  case EB_ALPHA: applyAlpha();
  case EB_BRAVO: applyBravo();
}

我想这样做:

Base* message = someMessage();
message->apply(this); // use polymorphism to determine what function to call.

我已经看到很多方法可以做到这一点,即使是基本的switch语句,它们看起来都不那么优雅。使用dyanimc_cast,从每次添加新消息时需要更新的messageHandler类继承,使用函数指针的容器,所有似乎都无法通过用多态替换开关来使代码更容易维护。

这是我能得到的:(我使用模板来避免从一个全知的处理程序接口继承)

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";
    }
};

显然,这不会编译,但我希望它能解决我的问题。

4 个答案:

答案 0 :(得分:3)

好吧,在屈服于dynamic_cast和多重继承之后,我想到了Anthony Williamsjogear.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;
}

答案 1 :(得分:2)

看起来你正试图找到某种双调度系统。查看访客模式或其他多调度系统。

答案 2 :(得分:2)

你的Bravo和Alpha类实际上是关闭的......太糟糕的C ++不直接支持它们。

您可以使用成员指针执行此操作:

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);

(语法可能不准确,但你知道我的意思是帽子)

答案 3 :(得分:0)

我不一定对你的设计模式问题有答案(尽管Modern C++ Design有很多话要说),但我确实希望解决你的转换与继承评论。

简单的swtich语句的问题是可维护性。如果那个switch语句在1个位置,那么它可能与创建类和继承的输入量大致相同,但是这个switch语句仍然是一个滴答作响的定时炸弹,等待另一个状态添加而没有为它添加一个案例。如果断言默认值:,你会在运行时捕获它 - 最终,但这很糟糕。如果你在表的大小上设置了一堆函数指针和编译时断言,那你就会做得更好,但这比switch语句更深。只要你在代码中有第二个位置需要检查状态,这一切就会全部消失。

一旦你的接口类设置让编译器处理内部开启状态的所有垃圾代码,这就容易多了。只要您按照界面添加该类就不必担心任何其他代码。