代码之家  ›  专栏  ›  技术社区  ›  Kyle

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

  •  2
  • Kyle  · 技术社区  · 15 年前

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

    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 回复  |  直到 15 年前
        1
  •  3
  •   Kyle    15 年前

    好吧,在放弃了动态投射和多重继承之后,我想到了这个,多亏了 Anthony Williams 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;
    }
    
        2
  •  2
  •   Edward Strange    15 年前

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

        3
  •  2
  •   Arkadiy    15 年前

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

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

        4
  •  0
  •   Michael Dorgan    15 年前

    我不一定能回答你的设计模式问题(尽管 Modern C++ Design 关于它有很多话要说),但我确实想处理您的开关vs继承注释。

    这个简单的swtich语句的问题是可维护性。如果switch语句位于1个位置,那么创建类和继承类的输入量可能是相同的,但是switch语句仍然是一个定时炸弹,等待添加另一个状态而不添加实例。如果您断言默认值:,您将在运行时捕获它—最终,但这非常糟糕。如果您在表的大小上设置了一组函数指针和编译时断言,那么您会做得更好,但这是比switch语句更深的另一个层次。当代码中有第二个位置需要检查状态时,这些都会从窗口消失。

    一旦你有了接口类的设置,让编译器处理内部打开状态的所有垃圾代码,这就简单多了。只要遵循接口,添加类就不需要担心任何其他代码。