6

I have a program which should read commands from the console and depending on the command perform one of several actions. Here is what I have so far:

void ConwayView::listening_commands() {
    string command;

    do {
        cin >> command;

        if ("tick" == command)
        {
            // to do
        }
        else if ("start" == command)
        {
            // to do for start
        }
        ...

    } while (EXIT != command);
}

Using a switch in place of the if statements helps a little if there are a large amount of commands. What patterns do you suggest using to provide the interactive command line?

Stephane Rolland
  • 38,876
  • 35
  • 121
  • 169
Dima00782
  • 171
  • 1
  • 3
  • 13

6 Answers6

6

There are multiple ways to solve this and it's debatable what the "right" solution is. If I were to solve it for my own work, I would create a table of a custom struct. Something like:

struct CommandStruct {
    char *command;
    int (*commandHandler)(/*params*/);
} commandTable[] = {
    { "tick",  tickCommand },
    { "start", startCommand },
    ...
};

Then my processing loop would walk through each element of this table, looking for the right match, such as:

for (int i = 0; i < TABLE_SIZE; ++i) {
    if (command == commandTable[i].command) { /* using whatever proper comparison is, of course */
        commandTable[i].commandHandler(/*params*/);
        break;
    }
}
mah
  • 39,056
  • 9
  • 76
  • 93
  • this brings me back to the good old days with assembly language - where you had to keep things simple - here is +1 for still thinking like that – serup Sep 27 '16 at 09:47
6

Not really a pattern, but often a good approach:

#include <map>
#include <functional>
#include <string>
#include <iostream>

typedef std::map< std::string, std::function<void(void)> > command_dict;
//                                           ^^^^^^^^
//                               the signature of your commands. probably should have an error code.

void command1() { std::cout << "commanda" << std::endl; }
void command2() { std::cout << "commandb" << std::endl; }
void command3() { std::cout << "commandc" << std::endl; }

int main() {
  command_dict c;
  c["a"] = &command1;
  c["b"] = &command2;
  c["c"] = &command3;

  std::string input;
  while(std::getline(std::cin, input)) { // quit the program with ctrl-d
    auto it = c.find(input);
    if(it != end(c)) {
      (it->second)(); // execute the command
    } else {
      std::cout << "command \"" << input << "\" not known" << std::endl;
    }
  }
}
pmr
  • 58,701
  • 10
  • 113
  • 156
4

If the number of command is small and possible parameters are really few, you could keep on with switch case !

If the number of commands increases, consider the command design pattern (which is IMHO some sort of strategy pattern disguised: cf Using a strategy pattern and a command pattern for the differences between command and strategy patterns).

If most of your commands are all sharing a part of the same behaviour, don't forget the template method pattern.

If the complexity for creating your command objects increases ( i.e. there is complexity in decoding/understanding the input of your command line), you should start looking at the interpreter design pattern

If while designing with the help of the interpreter pattern, you happen to see some complexity ( if the interpreter needs too much work, you see syntax issues and so on ), then you should probably look at DSL, domain specific language, and design your own language that fits (and only fits) to you own inputs.

FooF
  • 4,323
  • 2
  • 31
  • 47
Stephane Rolland
  • 38,876
  • 35
  • 121
  • 169
0

The if-else ladder is fine.

It can in principle be replaced with a map<string, Function>, but that gains you nothing for this concrete case (it is added complexity for no particular gain, even with a high number of commands).

When I wrote this initially I forgot to mention though:

  • Make your command handlers separate functions.

If you don’t, then the if-else ladder can become quite messy… The map solution requires separate functions, and can therefore appear to be a little more clean than an everything-directly-here if-else ladder. But it’s really the separate functions that then provide a measure of clarity, while the map detracts a little (adding extra work in maintaining the map, and an extra level of indirection to cope with).

On the other hand, since “read commands from the console” indicates interactive input, with a user in the picture, you don’t want to read two or more commands from the same line of input. Because that would screw up the prompting, and can seem quite baffling to the user. So instead of reading a “word” of input at a time using >>, use std::getline to a read a full line of input at a time.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
-4

Use the new and improved way to preform a bunch of commands at will:

int happy_time = 5;
int a = happy_time;
int muddy_dirt = 1;
int b = muddy_dirt;
int c = happy_time * muddy_dirt //really messy stuff

that's probably the least complicated way to do it...

Neo Anderson
  • 5,957
  • 2
  • 12
  • 29
-8

You must use database like access if your command is large.

Echilon
  • 10,064
  • 33
  • 131
  • 217
user1816090
  • 13
  • 1
  • 3
  • 2
    Using an external database to perform an action based on a command name? That doesn't really make sense. It's not like you can store C++ functions in an Access database. – interjay Nov 12 '12 at 13:53
  • Using a database to manage command processing would be like killing flies with a cannon. – mah Nov 12 '12 at 16:08
  • @user1816090: Yes, you can store the command names in a database. How would that in any way help you do what the question asks? – interjay Nov 12 '12 at 17:14
  • to perform faster than using for loop or more if statement – user1816090 Nov 13 '12 at 13:13
  • did you mean SQL (structured query language) when you say database like access ? if so then I understand your reasoning, however perhaps you should add extra on your answer to avoid confusion – serup Sep 27 '16 at 10:15