如何将“命令处理程序映射”从派生类重构为基类?

问题描述

我试图通过将某些通用功能从派生类移到基类来重构一些C ++代码。假设我有一个基类Fruit和两个派生类AppleOrange。这两个派生类目前都拥有一个私有映射,该映射将命令映射到成员函数,例如对于Apple类,将是

typedef void (Apple::*CommandHandler)();
static const std::map<std::string,CommandHandler> commandhandlers;

由于commandhandlers映射不会随时间变化,因此我将其设为静态,并且对于每个派生类,它都使用静态函数(例如)填充了命令处理程序。用于Apple类:

static std::map<std::string,Apple::CommandHandler> mpInitCommandHandlerMap()
{
    std::map<std::string,Apple::CommandHandler> commandhandlers;
    commandhandlers.insert(std::make_pair("eat",&Apple::eat));
    // ... and so on...
    return commandhandlers;
}

其中

void eat() { std::cout << "eating apple" << std::endl; }

eat类中Apple命令的(专用)命令处理程序的示例。

AppleOrange的派生类还具有handle函数来处理不同的命令:

void handle(const std::string& command)
{
    const auto handler = commandhandlers.at(command);
    (this->*handler)();
}

由于两个导出类的此handle函数相同,因此我想将其移至Fruit类。但是,这就是我遇到的问题。 commandhandlers映射当前同时存在AppleOrange类中,并且命令处理函数的类型不同(typedef void (Apple::*CommandHandler)();类的Appletypedef void (Orange::*CommandHandler)();类的Orange

所以我的问题是:我想在commandhandler类中只有一个handle映射和一个Fruit函数。我该如何做(最好现在使用C ++ 14)?完整的代码可在https://godbolt.org/z/87zbGa

在线获得

解决方法

您可以使用CRTP进行分解,例如:

template <typename Derived>
class Fruit
{
public:
    void handle(const std::string& command)
    {
        const auto handler = commandhandlers.at(command);
        (static_cast<Derived*>(this)->*handler)();
    }

protected:
    using CommandHandler = void (Derived::*)();
    static const std::map<std::string,CommandHandler> commandhandlers;
};

template <typename Derived>
const std::map<std::string,typename Fruit<Derived>::CommandHandler>
Fruit<Derived>::commandhandlers = Derived::mpInitCommandHandlerMap();

然后

class Apple : public Fruit<Apple>
{
friend class Fruit<Apple>;
private:
   
    static std::map<std::string,CommandHandler> mpInitCommandHandlerMap()
    {
        return {
            {"eat",&Apple::eat}
            // ... and so on...
        };
    }

    void eat() { std::cout << "eating apple" << std::endl; }
};

Demo

我在AppleOrange之间删除了通用基类,如果需要可以重新引入。

,

将命令处理程序映射和handle函数移动为Fruit基类为

class Fruit
{
public:
    
    void handle(const std::string& command)
    {
        const auto handler = commandhandlers_.at(command);
        handler();
    }

protected:
    std::map<std::string,std::function<void()>> commandhandlers_;
};

,然后在派生类中初始化命令处理程序映射,例如对于苹果

    Apple()
    {
        commandhandlers_ = mpInitCommandHandlerMap();
    }

mpInitCommandHandlerMap是非静态的似乎有效。另请参见https://godbolt.org/z/jx1cs8

的完整解决方案