Как сделать рефакторинг этой структуры, если-еще-если-еще-если * 100 - PullRequest
11 голосов
/ 26 октября 2011

Есть немного неприятного устаревшего кода.

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's instance
    map.insert(command, a function pointer);
}

// then

ICommand* pCommand = map.getInstance(command);

Но этот путь должен выполнять дополнительную функцию каждый раз, когда приходит новая команда.Да, функции могут быть разумными.Но все функции просто должны быть return new CommandNNN(); Я думаю, что есть способ удалить дублирование.

Как вы думаете?

Ответы [ 5 ]

10 голосов
/ 26 октября 2011

Поскольку все функции 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 все равно будет некоторое дублирование.Это может быть дополнительно уменьшено с помощью макросов, если это ваша чашка чая:

#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());
}
6 голосов
/ 26 октября 2011

Если вы используете C ++ 11, вы можете использовать встроенные лямбда-выражения, чтобы все было в одном месте:

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();
    }));
1 голос
/ 26 октября 2011

просто разобрать строку, чтобы вернуть int, а затем пройти через переключатель.это должно быть быстро и мало.case s могут быть сгенерированы довольно легко, если это необходимо.Пример довольно очевиден:

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!");
...

извините, сегодня нет особенных языковых возможностей TT.Конечно, вы можете запустить это через макрос и сделать его меньше символов, или вы можете пометить его как сгенерированный код и сделать это за 2 минуты.

1 голос
/ 26 октября 2011

Вы можете поместить свою карту в качестве частного члена вашей фабрики, как:

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 - это интерфейс, поэтому вызовите виртуальный метод invoke ()

class ICommand{
  public:
    ICommand();
    virtual bool invoke()=0;
    virtual ~ICommand();
};

Но обычно простой фабрики может быть достаточно.

1 голос
/ 26 октября 2011

Почему бы просто не сделать статический массив

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))();

Или что-то в этом роде?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...