c++ - 寻找设计模式以减少虚拟方法的重载

标签 c++ c++11

我有大量的(〜100)类是从一个通用基类(Device)派生的。每个设备都可以接受大量类似命令的某些子集。不同的命令可以具有不同数量和类型的参数,因此每个命令都按其自己的类型封装(如有必要,可以更改)。

在仅给Device基类提供指针/引用的情况下,有什么模式可以使我将命令传递给设备,以便设备可以访问命令的类型和参数?

我提出的选项:

  • 最直接的方法是在基类Device中添加一个接受每种命令类型的单独虚拟方法。但是,这最终将在基类中使用大量虚方法,而这些虚方法仅在很少的派生类中被覆盖。
  • 我考虑了访问者模式,但是由于命令类的数量大约等于设备类的数量,因此这实际上并没有获得任何好处。
  • 使用RTTI(或每个命令唯一的枚举/标识符)确定命令类型,然后使用switch / if分支到适当的代码。这感觉很脏,因为它绕过了常规的C++多态性。另外,dynamic_cast在这里非常令人讨厌,因此几乎没有这个选项。

  • 对于在Device基类中没有大量虚拟方法的情况下,可以干净利落地使用此模式的任何建议?

    最佳答案

    这是经典的double dispatch problem

    我已经遇到过这种模式几次,并使用以下策略来处理它。

    假设基类Command具有一个返回“id”的函数,该ID可以是整数类型,字符串类型,也可以用作映射中的键。

    struct Command
    {
       typedef  <SomeType> IDType;
       virtual IDType getID() const = 0;
    };
    
    Device的接口(interface)可以简化为:
    struct Command;
    struct Device
    {
       virtual execute(Command const& command) = 0;
    };
    

    假设DeviceABCD是派生类型之一,并且通过基类指针/引用操作的实际设备是DeviceABCD。在第一次调度中,将执行命令的调用调度到DeviceABCD::execute()
    DeviceABCD::execute()的实现将其分派(dispatch)给另一个执行实际工作的函数。

    您需要一个适当的框架来正确执行第二次调度。在框架中:
  • 需要有“命令ID”->“命令执行器”的映射。
  • 在给定“命令ID”的情况下,需要一种方法来注册“命令执行器”。

  • 基于这些,您可以在给定“命令ID”的情况下获得“命令执行器”。如果存在“命令执行器”,则可以简单地将命令执行分派(dispatch)到“命令执行器”。如果不是,则需要处理错误,最有可能通过引发异常来处理。

    该框架可用于Device的所有子类型。因此,可以使用Device本身或与Device对等的帮助程序类来实现该框架。我更喜欢第二种方法,建议创建几个类:CommandExecutorCommandDispatcher

    CommandExecutor.h:
    struct CommandExecutor
    {
       virtual execute(Command const& command) = 0;
    };
    

    CommandDispatcher.h:
    class CommandDispatcher
    {
       public:
          void registerCommandExecutor(Command::IDType commandID,
                                       CommandExecutor* executor);
    
          void executeCommand(Command const& command);
    
          std::map<Command::IDType, CommandExecutor*>& getCommandExecutorMap();
    
       public:
          std::map<Command::IDType, CommandExecutor*> theMap;
    };
    

    CommandDispatcher.cpp:
    void CommandDispatcher::registerCommandExecutor(Command::IDType commandID,
                                                    CommandExecutor* executor)
    {
       getCommandExecutorMap()[commandID] = executor;
    }
    
    void CommandDispatcher::executeCommand(Command const& command)
    {
       CommandExecutor* executor = getCommandExecutorMap()[commandID];
       if ( executor != nullptr )
       {
          executor->execute(command);
       }
       else
       {
          throw <AnAppropriateExecption>;
       }
    }
    
    std::map<Command::IDType, CommandExecutor*>& CommandDispatcher::getCommandExecutorMap()
    {
       return theMap;
    }
    

    如果DeviceABCD可以执行Command12Command34,则其实现将类似于:

    DeviceABCD.cpp:
    struct Command12Executor : public CommandExecutor
    {
        virtual void execute(Command const& command) { ... }
    };
    
    struct Command34Executor : public CommandExecutor
    {
        virtual void execute(Command const& command) { ... }
    };
    
    DeviceABCD::DeviceABCD() : commandDispatcher_(CommandExecutor)
    {
       static Command12Executor executor12;
       static Command34Executor executor34;
    
       // This assumes that you can get an ID for all instances of Command12
       // without an instance of the class, i.e. it is static data of the class.
    
       commandDispatcher_.registerExecutor(Command12Type, &executor12);
       commandDispatcher_.registerExecutor(Command34Type, &executor34);
    }
    

    有了该框架,DeviceABCD::execute()的实现就非常简单。
    void DeviceABCD::execute(Command const& command)
    {
       commandDispatcher_.executeCommand(command);
    }
    

    精简到可以在基类中实现的程度。仅当需要在命令被分派(dispatch)到正确的CommandExecutor之前对命令进行按摩或更新某些其他状态时,才需要在派生类中实现它。

    关于c++ - 寻找设计模式以减少虚拟方法的重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33613155/

    相关文章:

    c++ - Microsoft Visual Studio 2010 没有将构建工具放在 PATH 中以获得干净的目标?

    c++ - 如何从 C 文件调用 Cpp 函数

    c++ - 存在内存泄漏问题的链表

    c++ - std::set 迭代器的返回类型冲突

    c++ - 在 C++ 中查找数组中总和为零的所有唯一三元组

    c++ - 使用 c++11 的 <random> header ,获取 0 到 n 之间的整数的正确方法是什么?

    c++ - 如何为泛型类型的 vector 声明迭代器?

    c++ - 对象集的排序不正确

    c++ - 什么时候应该存储函数的引用或指针?

    c++ - 为什么用 brew 安装 fmt 和 gcc 编译器后找不到 fmt 库?