我会喜欢这个问题的一些意见,我正在努力锻炼。我正在努力提高我的OO体验并充分利用C ++的多态功能。我正在尝试为基本命令解析器编写一些代码。他们的命令结构如此:
[命令名称] [参数]
命令名称仅限于一个单词字符串。参数可以是0到N的字符串列表。
每个命令和参数列表都可以指向我系统中的各种软件对象。因此,例如,我可以将rtp statistics命令映射到我的rtp模块,用户统计信息到我的用户模块。这样的事情。
现在,我的CLI的入口点将整个命令字符串作为标准字符串提供。它还提供了一个标准输出流,用于向用户显示结果。
我真的想避免使用解析器函数然后执行if if then else交易。所以我在想这样的事情:
我正在努力的是如何为正确的命令提供正确的模块。这是我应该使用模板参数的地方吗?这样每个命令都可以接受任何接口,我会让工厂决定将哪个模块传入命令对象?
我也对其他意见持开放态度。我只是想学习并希望社区可以给我一些提示: - )。
答案 0 :(得分:7)
您正在寻找的是OOP中的常见模式。 Design Patterns(“四人帮”一书)将此称为Command Pattern。
通常不需要模板。所有内容都在运行时进行解析和分派,因此动态多态(虚函数)可能是更好的选择。
在另一个答案中,拉斐尔·巴普蒂斯塔提出了一个基本设计。以下是我将他的设计修改为更完整的方法:
命令由Command
类的子类处理。命令由CommandDispatcher
对象调度,该对象处理命令字符串的基本解析(基本上,在空格处分割,可能处理引用的字符串等)。
系统使用Command
注册CommandDispatcher
的实例,并将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
或其他中心位置使用Singleton模式实例化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后,您的代码可以确定是否存在这样的函数,然后调用它。
答案 1 :(得分:2)
模板太过分了。我想你需要一些命令解释器只能从可用的对象中找出可能的命令。
对于每个想要支持这个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的类。