Как сделать рефакторинг этой структуры if-else-if-else-if * 100
Существует несколько неприятных устаревших кодов.
std::string xxx = GetCommand(); // get "CommandX";
if (xxx == "Command1")
{
return new Command1();
}
else if (xxx == "Command2")
{
return new Command2();
}
...
else if (xxx == "Command100")
{
return new Command100();
}
Я хочу улучшить эту структуру кода.
Слишком много сравнения. Поэтому я помещаю их на карту.
for (int i = 0; i < GetCommandCount(); ++i)
{
// key is a command string
// value is a function pointer which creates it instance
map.insert(command, a function pointer);
}
// then
ICommand* pCommand = map.getInstance(command);
Но этот способ должен выполнять дополнительную функцию каждый раз, если приходит новая команда.
Да, функции могут быть разумными. Но все функции просто return new CommandNNN();
Я думаю, что есть способ удалить дублирование.
Как вы думаете?
Ответы
Ответ 1
Так как все функции return new CommandNNN();
, вы можете использовать функцию шаблона:
template <class T>
CommandBase* createCommand() {
return new T();
}
и привяжите эту функцию к вашей карте:
map.insert(std::make_pair("Command1", &createCommand<Command1>));
map.insert(std::make_pair("Command2", &createCommand<Command2>));
map.insert(std::make_pair("Command3", &createCommand<Command3>));
Это позволяет избежать создания новой функции для каждой команды. Однако в map.insert
-statements все равно будет некоторое дублирование. Это может быть дополнительно уменьшено с помощью макросов, если ваша чашка чая:
#define INSERT(cmd) map.insert(std::make_pair(#cmd, &createCommand<cmd>));
INSERT(Command1);
INSERT(Command2);
INSERT(Command3);
#undef INSERT
или
#define INSERT(n) map.insert(std::make_pair("Command" #n, &createCommand<Command ## n>));
INSERT(1);
INSERT(2);
INSERT(3);
#undef INSERT
Я подозреваю, что вы даже можете заставить препроцессор делать некоторые подсчеты для вас, но не за пределами моего домена.
Применяя еще больше макросов, а также некоторое глобальное состояние, на которое многие недовольны, вы можете получить еще более тугое соединение:
#include <map>
#include <string>
#include <cassert>
class CommandBase {};
static std::map<std::string, CommandBase* (*)()> g_commandMap;
template <class C>
CommandBase* createCommand() {
return new C();
}
class CommandRegistrer {
public:
CommandRegistrer(const std::string& name, CommandBase* (*instantiator)()) {
g_commandMap.insert(std::make_pair(name, instantiator));
}
};
#define COMMAND_CLASS(n) \
class Command##n; \
CommandRegistrer g_commandRegistrer##n("Command" #n, createCommand<Command##n>); \
class Command##n : public CommandBase
COMMAND_CLASS(1) { /* implementation here */ };
COMMAND_CLASS(2) { /* implementation here */ };
int main() {
assert(g_commandMap.find("Command1") != g_commandMap.end());
}
Ответ 2
Если вы используете С++ 11, вы можете использовать встроенные lambdas для этого, чтобы все было в одном месте:
class Object
{
};
class Command1 : public Object
{
};
// etc
typedef std::map<std::string, std::function<Object*()>> FunctionMap;
typedef std::pair<std::string, std::function<Object*()>> FunctionPair;
FunctionMap funcMap;
funcMap.insert(FunctionPair("Command1", []()
{
return new Command1();
}));
Ответ 3
Почему бы просто не сделать статический массив
static struct cmdthing {
const char *cmd;
void (*fun)();
} commands[] = {
{..,..},
{..,..},
...
};
for(const cmdthing *p=commands;p<commands+sizeof(commands)/sizeof(*commands);++p)
if(!strcmp(p->cmd,cmd)) return (*(p->fun))();
Или что-то в этом роде?
Ответ 4
Вы можете разместить свою карту как частный член вашего factory, например:
CommandFactory{
private:
std::map< std::string, ICommand*> m_commands;
public:
CommandFactory();
ICommand* getInstance()const;
virtual ~CommandFactory();
};
В регистре конструктора все ваши действия, например:
CommandFactory(){
m_commands.insert( std::pair< std::string, ICommand*>("commandName", new Command) );
}
ICommand - это интерфейс, поэтому вызовите виртуальный метод()
class ICommand{
public:
ICommand();
virtual bool invoke()=0;
virtual ~ICommand();
};
Но обычно простой factory может быть довольно.
Ответ 5
просто проанализируйте строку, чтобы вернуть int, затем перейдите через переключатель. это должно быть быстрым и малым. case
может быть сгенерирован достаточно легко, если необходимо. образец довольно очевидный:
int ToCommandID(const std::string& CommandX) { evaluate and return X as an int }
Command* NewCommand() {
const std::string xxx(GetCommand()); // get "CommandX";
const int commandID(ToCommandID(xxx));
switch (commandID) {
case 1 : return new Command1();
case 2 : return new Command2();
case 3 : return new Command3();
case 4 : return new Command4();
case 5 : return new Command5();
case 6 : return new Command6();
case 7 : return new Command7();
case 8 : return new Command8();
case 9 : return new Command9();
case 10 : return new Command10();
case 11 : return new Command11();
case 12 : return new Command12();
case 13 : return new Command13();
case 14 : return new Command14();
...
default : {
assert(0 && "oh no!");
...
Извините, никаких фантазийных особенностей языка сегодня нет. Конечно, вы можете запустить это через макрос и сделать его меньше символов, или вы можете пометить его как сгенерированный код и сделать это через 2 минуты.