Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do refactoring this structure, if-else-if-else-if * 100

There is some nasty legacy code.

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

I want to improve this code structure.
There were too many comparison. So I put them to a map.

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

But this way has to make additional function every time if new command comes. Yes, the functions might be reasonable. But all the functions just be return new CommandNNN(); I guess there is the way to remove the duplication.

How do you think?

like image 223
Benjamin Avatar asked Oct 26 '11 09:10

Benjamin


4 Answers

Since all the functions are return new CommandNNN();, you can use a template function:

template <class T>
CommandBase* createCommand() {
    return new T();
}

and bind to this function in your map:

map.insert(std::make_pair("Command1", &createCommand<Command1>));
map.insert(std::make_pair("Command2", &createCommand<Command2>));
map.insert(std::make_pair("Command3", &createCommand<Command3>));

This lets you avoid creating a new function for each command. However, there would still be some duplication in the map.insert-statements. This could be further reduced by using macros, if that's your cup of tea:

#define INSERT(cmd) map.insert(std::make_pair(#cmd, &createCommand<cmd>));

INSERT(Command1);
INSERT(Command2);
INSERT(Command3);

#undef INSERT

or

#define INSERT(n) map.insert(std::make_pair("Command" #n, &createCommand<Command ## n>));

INSERT(1);
INSERT(2);
INSERT(3);

#undef INSERT

I suspect that you can even get the preprocessor to do some counting for you, but that's outside of my domain.


Applying even more macros, as well as some global state, both of which are frowned upon by many, you can get even tighter coupling:

#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());
}
like image 171
Magnus Hoff Avatar answered Nov 09 '22 04:11

Magnus Hoff


If you are using C++11, you can use inline lambdas to do it so that everything is in one place:

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();
    }));
like image 23
Moo-Juice Avatar answered Nov 09 '22 03:11

Moo-Juice


Why not just make a static array

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

Or something like that?

like image 1
Michael Krelin - hacker Avatar answered Nov 09 '22 04:11

Michael Krelin - hacker


You can put your map as private member of your factory like:

CommandFactory{
 private:
  std::map< std::string, ICommand*> m_commands;
 public:
  CommandFactory();
  ICommand* getInstance()const;
  virtual ~CommandFactory();
};

In the constructor register all your actions like:

CommandFactory(){
  m_commands.insert( std::pair< std::string, ICommand*>("commandName", new Command) );
}

ICommand is an interface so make a virtual method invoke()

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

But normally an simple factory could be anough.

like image 1
AlexTheo Avatar answered Nov 09 '22 05:11

AlexTheo