【问题标题】:Find a command handler from a command type in modern C++从现代 C++ 中的命令类型中查找命令处理程序
【发布时间】:2021-10-28 01:24:41
【问题描述】:

我有一堆名称和数据不同的结构(命令)。我有一堆命令处理程序。客户端代码向中介者发送一个结构体,中介者应该根据命令(结构体)类型找到具体的命令处理程序。如何在现代 C++ 中不使用 switch/if 语句来做到这一点?

#include <iostream>

struct Command {};

struct CommandA : Command
{
    int a = 32;
};

struct CommandB : Command
{
    double b = 56.43;
};

class ACommandHandler
{

public:
    void Process(CommandA command) { std::cout << command.a; }
};

class BCommandHandler
{

public:
    void Process(CommandB command) { std::cout << command.b; }
};

class Intermediator
{
public:
    void Delegate(Command command)
    {
    //How can I choose a concrete command handler here?
    }

private:
    ACommandHandler handlerA;
    BCommandHandler handlerB;
};

int main()
{
    Intermediator intermediator;
    CommandA a;
    intermediator.Delegate(a);
    return 0;
}

【问题讨论】:

  • 您的意思是传递对void Delegate(Command command) 的引用吗?除非您可以将Delegate 设为模板或将其参数更改为std::variant 之类的东西,否则这是不可能的。
  • 为什么需要单独的处理程序类?为什么不在每个命令中添加void Process()
  • 因为命令是由用户发送的,只有服务器应该知道如何处理这些命令。
  • 您可以创建一个查找表,而不是 switch 或 if-else 链。该表可以是静态的(最简单的),或者每个命令可以在程序启动时动态注册(更复杂,但更灵活)。
  • 拜托,你能做一个简单的代码示例吗? 之类的东西,只有一个委托函数,它将使用此表将任何命令分配给具体处理程序?

标签: c++ oop polymorphism


【解决方案1】:

嗯。据我了解,您的设计或解决问题的方法可能是错误的。也许,我们在这里谈论的是XY-Problem

了解您真正想要实现的目标会非常有趣。

那么,我的下一个观察:我认为你还没有完全理解多态性的含义或者如何正确使用它。

在您的示例代码中,根本没有 多态性。没有虚函数之类的。

而且您的CommandHandler 不是从公共基类派生的。乍一看,CommandHandlerCommand 之间似乎存在关系。但是没有。只有一个相同的命名。例如 BCommandHandler 也可以有一个process 函数,该函数适用于类型 CommandA。这在某种程度上是一个奇怪的概念。 HandlerCommand 之间应该有关系。

最好将process 函数作为命令类的一部分。那将是一个更有意义的设计,并且会消除几乎所有的问题。但正如所说:我不知道你为什么选择这样的设计。

接下来,即使我们有一个CommandHandler 的类层次结构并尝试通过CommandHandler 的基类指针调用虚函数,我们仍然有一个基类作为函数的参数指向Command 的指针。

这需要所谓的双重调度功能。因此,被调用对象和给定参数的多态性。不幸的是,C++ 语言不支持双重调度机制。您可以通过使用 GOF 中的 Visitor 模式来模拟它。但众所周知,这种模式难以理解和实现,并且会产生循环引用的问题。在 Andrei Alexandrescu 的“Modern C++ Design: Generic Programming and Design Patterns Applied”中可以找到一个非常好的解决方案。在那里您可以阅读有关非循环访问者模式的信息。很好的解释。

另一种解决方案是使用模板来实现静态/编译时多态性。但是你说你有很多commands。而且我担心你会得到一些实质性的代码膨胀。

但是由于handlercommand 之间存在一些固定关系,您可以使用std::map 和一些RTTI 机制来解决您的问题。还有dynamic_cast

不太好,可能很贵,但可以按以下方式使用:

#include <iostream>
#include <map>
#include <string>
#include <utility>

struct Command { virtual void someDummyVirtualFunctionDoingNothing() {} };

struct CommandA : public Command { int i{ 42 }; };
struct CommandB : public Command { double d{ 43.2 }; };
struct CommandC : public Command { unsigned u = 44u; float f{ 45.6 }; };

struct CommandHandler {
    virtual void process(Command*) = 0;   // pure virtual function. Make Base class abstract
};
struct CommandHandlerA : public CommandHandler {
    void process(Command* com) {
        if (CommandA* ca = dynamic_cast<CommandA*>(com))
            std::cout << "Command Handler A:  " << ca->i << '\n';
    }
};
struct CommandHandlerB : public CommandHandler {
    void process(Command* com) {
        if (CommandB* cb = dynamic_cast<CommandB*>(com))
            std::cout << "Command Handler B:  " << cb->d << '\n';
    }
};
struct CommandHandlerC : public CommandHandler {
    void process(Command* com) {
        if (CommandC* cc = dynamic_cast<CommandC*>(com))
            std::cout << "Command Handler C:  " << cc->u << '\t' << cc->f << '\n';
    }
};
class Intermediator
{
public:
    void delegate(Command* command)
    {
        const std::string key{ typeid(*command).name() };
        if (dispatcher.find(key) != dispatcher.end()) {
            dispatcher[key]->process(command);
        }
    }
    // Commands are just dummies and used to get the type
    void addDelegate(CommandHandler* handler, Command* command) {
        const std::string key{ typeid(*command).name() };
        dispatcher[key] = handler;
    }
protected:
    std::map<std::string, CommandHandler*> dispatcher{};
};

int main() {
    // Define instances of command classes 
    Command* commandA = new CommandA();  // Dummy. Only needed for type deduction
    Command* commandB = new CommandB();  // Dummy. Only needed for type deduction
    Command* commandC = new CommandC();  // Dummy. Only needed for type deduction
    Command* commandAA = new CommandA();
    Command* commandBB = new CommandB();
    Command* commandCC = new CommandC();

    // Define Instances of command handler
    CommandHandler* commandHandlerA = new CommandHandlerA();
    CommandHandler* commandHandlerB = new CommandHandlerB();
    CommandHandler* commandHandlerC = new CommandHandlerC();

    // Add delegate function for types.
    // All pointers here are pointer to the base classes.
    // Here the commands are just dummies and only used to get the type.
    Intermediator im{ };
    im.addDelegate(commandHandlerA, commandA);
    im.addDelegate(commandHandlerB, commandB);
    im.addDelegate(commandHandlerC, commandC);

    // Call delegate. Please note. The commands are pointers to the abstract base Class 
    im.delegate(commandAA);
    im.delegate(commandBB);
    im.delegate(commandCC);

    return 0;
}

请注意:拥有内存的原始指针应替换为std::unique_ptr 或其他智能指针。

而且,如上所述,我仍然认为这是错误的设计。

也许抽象工厂可以帮助你。

【讨论】:

    【解决方案2】:
    #include <iostream>
    #include <type_traits>
    
    struct CommandA
    {
        auto Do() const { return m_data; }
    private:
        int m_data = 42;
    };
    
    struct CommandB
    {
        auto Do() const { return m_data; }
    private:
        float m_data = 3.1415f;
    };
    
    struct CommandC
    {
        auto DoC() const
        {
            std::cout << "C" << std::endl;
        }
    };
    
    class Intermediator
    {
    public:
        template<typename command_t>
        auto Delegate(const command_t& command)
        {
                return command.Do();
        }
    
        // this will be the most specialized handler for CommandC
        auto Delegate(const CommandC& command)
        {
            return command.DoC();
        }
    };
    
    int main()
    {
        CommandA a;
        CommandB b;
        CommandC c;
    
        Intermediator intermediator;
        
        auto value = intermediator.Delegate(a);
        std::cout << value << std::endl;
    
        auto pi = intermediator.Delegate(b);
        std::cout << pi << std::endl;
    
        intermediator.Delegate(c);
    
        return 0;
    }
    

    【讨论】:

    • 太好了,谢谢。但是如何使用此解决方案调用具体的命令处理程序?而且,每个结构应该只包含一些数据。
    • 嘿,我更新了这个例子来做到这一点。但不确定这是否是你最终真正需要的。 .. 看起来您需要在代码中添加一个 dynamic_cast,因为您将一个基类传递给了 Intermediator。
    • 我需要一个不使用 if 和 switch 语句的解决方案。我的系统处理了一百多个命令,所以它对我不起作用,但是谢谢。
    • "if constexpr" 在编译时被评估!运行时代码将没有 if/then/else :) 基本上,将为每种命令类型生成一个专门的 Delegate 函数并直接调用。我只是意识到您可以为每种类型创建一个委托重载,并且它的行为将是相同的 :) 只需拥有 Delegate(const CommandA&) 和一个 Delegate(const CommmandB&)。如果所有命令都具有相同的方法,那么模板会更短(if constexpr 再次消失)
    • 我为造成的混乱道歉 :) 再次更新了示例
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-23
    • 2011-03-27
    • 2012-03-08
    • 2021-09-09
    • 2021-08-01
    • 2021-12-31
    • 2021-11-24
    相关资源
    最近更新 更多