多态命令解析器设计
Polymorphic Command Parser Design
希望对我正在尝试解决的这个问题提出一些意见。 我正在努力改善我的OO体验,并充分利用C++的多态功能。 我正在尝试为基本命令解析器编写一些代码。 他们的命令结构是这样的:
[命令名称] [参数]
命令名称将仅限于一个单词字符串。参数可以是 0 到 N 的字符串列表。
每个命令和参数列表都可以定向到系统中任何类型的软件对象。 因此,例如,我可以将 rtp 统计信息命令映射到我的 rtp 模块,将用户统计信息映射到我的用户模块。 类似的东西。
现在,我的 CLI 的入口点将整个命令字符串作为标准字符串提供。 它提供了一个标准的输出流,用于向用户显示结果。
我真的很想避免使用解析器函数,然后做一个 if 然后是其他交易。 所以我在想这样的事情:
- 我会有一个名为命令的基类。 它的构造函数将采用字符串命令、stdout 和需要与之交互的对象的接口。
- 我将创建一个命令工厂,该工厂将命令名称与处理它的对象匹配。 这将实例化正确命令的正确命令对象。
- 每个单独的命令对象将分析给定的参数,并为此命令做出正确的选择。
我正在努力解决的是如何将正确的模块提供给正确的命令。 这是我应该使用模板参数的地方吗? 这样每个命令都可以采用任何接口,我会让工厂决定将哪个模块传递给命令对象?
我也对其他意见持开放态度。 我只是想学习,希望社区能给我一些提示:-)。
您要查找的是 OOP 中的常见模式。 设计模式(四人帮一书)将其称为命令模式。
通常不需要模板。 一切都在运行时解析和调度,因此动态多态性(虚函数)可能是更好的选择。
在另一个答案中,拉斐尔·巴普蒂斯塔提出了一个基本设计。 以下是我修改他的设计以使其更完整的方法:
命令对象和命令调度程序
命令由 Command
类的子类处理。 命令由处理命令字符串基本解析的 CommandDispatcher
对象调度(基本上,在空格处拆分,可能处理带引号的字符串等)。
系统向CommandDispatcher
注册一个Command
实例,并将每个Command
实例与命令名称(std::string
)相关联。 关联由 std::map
对象处理,尽管可以用哈希表(或用于关联键值对的类似结构)替换。
class Command
{
public:
virtual ~Command(void);
virtual void execute(FILE* in, const std::vector<std::string>& args) = 0;
};
class CommandDispatcher
{
public:
typedef std::map<std::string, Command*> CommandMap;
void registerCommand(const std::string& commandName, Command* command)
{
CommandMap::const_iterator cmdPair = registeredCommands.find(commandName);
if (cmdPair != registeredCommands.end())
{
// handle error: command already registered
}
else
{
registeredCommands[commandName] = command;
}
}
// possibly include isRegistered, unregisterCommand, etc.
void run(FILE* in, const std::string& unparsedCommandLine); // parse arguments, call command
void dispatch(FILE* in, const std::vector<std::string>& args)
{
if (! args.empty())
{
CommandMap::const_iterator cmdPair = registeredCommands.find(args[0]);
if (cmdPair == registeredCommands.end())
{
// handle error: command not found
}
else
{
Command* cmd = cmdPair->second;
cmd->execute(in, args);
}
}
}
private:
CommandMap registeredCommands;
};
我省略了解析和其他细节,但这是命令模式的一个非常常见的结构。 请注意std::map
如何处理将命令名称与命令对象相关联。
注册命令
要使用此设计,您需要在系统中注册命令。 您需要实例化CommandDispatcher
,无论是使用单例模式,还是在main
中,还是在另一个中心位置。
然后,您需要注册命令对象。 有几种方法可以做到这一点。 我更喜欢的方式,因为你有更多的控制权,是让每个模块(一组相关命令)提供自己的注册功能。 例如,如果您有一个"文件 IO"模块,那么您可能有一个函数fileio_register_commands
:
void fileio_register_commands(CommandDispatcher* dispatcher)
{
dispatcher->registerCommand( "readfile", new ReadFileCommand );
dispatcher->registerCommand( "writefile", new WriteFileCommand );
// etc.
}
在这里,ReadFileCommand
和WriteFileCommand
是实现所需行为的Command
的子类。
您必须确保在命令可用之前调用fileio_register_commands
。
此方法适用于动态加载的库(DLL 或共享库)。 确保注册命令的函数具有基于模块名称的常规模式:XXX_register_commands
,其中XXX
是例如小写的模块名称。 加载共享库或 DLL 后,代码可以确定此类函数是否存在,然后调用它。
模板是矫枉过正的。我想你想要一些东西,命令解释器只是从可用的对象中找出哪些命令是可能的。
对于想要支持此 CLI 的每个类,我会给它一个注册该类的函数,以及触发该类的命令名称。
class CLIObject
{
virtual void registerCli( Cli& cli ) = 0;
virtual bool doCommand( FILE* file, char** args ) = 0;
}
class HelloWorld : public ClIObject
{
void registerCli( Cli& cli ) { cli.register( this, "helloworld" ); }
bool doCommand( FILE* file, char** args )
{
if ( !args[0] ) return false;
fprintf( file, "hello world! %s", args[0] );
return true;
}
}
现在,您的 cli 可以支持从 CLIObject 派生的任何类。
- 多态性和功能结合
- 具有默认模板参数的多态类的模板推导失败
- 找不到成员对象:没有名为get_event()的成员,也处理多态性和向量
- 多态二进制函数
- 访问存储在向量C++中的结构的多态成员
- 使用取消引用的指针的多态性会产生意外的结果.为什么?
- 将 std::allocate_shared 与多态资源分配器一起使用
- 通过switch和static_cast访问多态对象的运行时类型
- C++boost序列化多态性问题
- 多态杆件变量 - 类设计
- 如何查找哪个类对象位于数组的特定索引上(多态性)
- 如何在多线程中正确使用unique_ptr进行多态性?
- Doees the 'this' 指针参与虚函数的多态行为
- C++ 在堆栈中包含多态属性的类对象存储
- 基类和派生类的多态赋值运算符
- 转身多态对象
- 如何在基类指针向量的元素上应用重载的多态函数
- 具有智能指针的多态性
- 当目标指针不是基类的类型时,为什么允许dynamic_cast为多态类生成 null 指针?
- 多态命令解析器设计