如何将此“命令处理程序映射”从派生类重构为基类?
Posted
技术标签:
【中文标题】如何将此“命令处理程序映射”从派生类重构为基类?【英文标题】:How to refactor this 'command handler map' from derived classes to the base class? 【发布时间】:2020-08-19 07:01:23 【问题描述】:我试图通过将某些通用功能从派生类移动到基类来重构一些 C++ 代码。假设我有一个基类Fruit
和两个派生类Apple
和Orange
。两个派生类目前都拥有一个私有映射,将命令映射到成员函数,例如对于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;
是Apple
类中eat
命令的(私有)命令处理程序示例。
Apple
和Orange
派生类也都有一个handle
函数来处理不同的命令:
void handle(const std::string& command)
const auto handler = commandhandlers.at(command);
(this->*handler)();
由于这个handle
函数对于两个派生类都是相同的,我想将它移到Fruit
类中。但是,这就是我卡住的地方。 commandhandlers
映射当前存在于Apple
和Orange
类中,并且具有不同类型的命令处理函数(typedef void (Apple::*CommandHandler)();
用于Apple
类和typedef void (Orange::*CommandHandler)();
用于Orange
类) .
所以我的问题是:我希望在Fruit
类中只有一个commandhandler
映射和一个handle
函数。我该怎么做(现在最好使用 C++14)?完整代码可在线获取https://godbolt.org/z/87zbGa
【问题讨论】:
将std:map<std:string, std::function<void()> > commandhandlers_;
放在您的基类中,并在您的Apple
或Orange
构造函数中正常初始化它:commandhandlers_["eat"] = std::bind(&Apple::eat, this);
@pptaszni 这可能与拥有static
命令处理程序映射的要求相冲突。否则,Apple
可能会“看到”Orange
的命令。
"可能会看到Oragne
的命令" - 我认为不可能。 Apple::Apple(params ... )
将使用只有Apple
可能知道的命令填充地图,反之亦然。如果我错了,请给我一些例子。在派生类中有static
映射的要求与能够从基类调用该映射有点冲突。
还有两点说明: 1) 请注意,从长远来看,Fruit 类还会将命令处理程序添加到命令处理程序映射中。这些将是 Fruit 类中定义的命令处理函数(= Apple 和 Orange 类的相同行为)。 2)我不完全确定命令处理程序映射是否确实需要是静态的......无论如何,Apple 命令处理程序不应该与 Orange 命令处理程序混淆!
是的,考虑到您的言论,我仍然认为我建议的解决方案是您所需要的。您无需担心混淆Apple和Orange命令,以防在对象上调用fruit->handle("apple_specific_command");
Orange
类型的键 apple_specific_command
不会出现在 commandhandlers_
映射中。
【参考方案1】:
您可以使用 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
我删除了 Apple
和 Orange
之间的通用基类,如果需要,您可以重新引入。
【讨论】:
这是一个非常有趣的解决方案。【参考方案2】:将命令处理程序映射和句柄函数移至 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的完整解决方案
【讨论】:
这里有个小缺点:commandhandlers_ 映射不会随时间改变,所以我想将其设为 const,但这是不可能的,因为它在 Apple 构造函数中获得了初始值。尝试在 Apple 的初始化器列表中初始化时,编译器当然会抱怨 Apple 没有字段名称 commandhandlers_ :-( 不,这里没有缺点。只需在构造函数初始化程序列表中初始化地图就可以了。 example 好的,确实可以将地图作为 Fruit 构造函数的参数。谢谢。以上是关于如何将此“命令处理程序映射”从派生类重构为基类?的主要内容,如果未能解决你的问题,请参考以下文章