3

For my situation I have a device receiving string commands such as turn_on, pump, etc. Some of these commands also have arguments attached to them such as an int.

I have methods like this

void turn_on()

and some with parameters.

void pump(int milliseconds)

I'm trying to find an approach that allows something like this :

commands.map_command('turn_on', &turn_on) 
// Call turn_on() when the command centre receives string `turn_on`

Further, it should also be able to handle arguments and automatically pass the argument

commands.map_command<int>('pump', &pump)
// Calls pump(millis) when command centre receives string `pump=millis`)

With this implementation, I believe the code would be cleaner. Currently it is a bunch of if statements after splitting the first part of the command and then parsing the rest of the command in each if branch. I'm looking for something that would avoid the former situation.

5
  • 4
    And how exactly do you plan to call these functions? I mean, at some point, you're going to have a string that you don't know what it contains, and you're going to have to call a function, maybe with an int and maybe without, and whether "with" or "without" is correct will depend on the value stored in that string. And thus, you've violated C++'s static typing constraints. Sep 27, 2020 at 3:19
  • @NicolBolas: I think that is part of the question here.
    – Doc Brown
    Sep 27, 2020 at 6:40
  • btw double quotes for string literals;-)
    – Christophe
    Sep 27, 2020 at 8:23
  • Can you elaborate how many commands we are talking about? You can get really advanced with script parsers and objectifying your primitive data types. But if this is for 10 commands, then there are easier solutions. Sep 27, 2020 at 9:52
  • Please provide some command calling examples. Sep 27, 2020 at 13:05

1 Answer 1

7

The standard approach when implementing the command pattern with command objects, derived from some Command class, is to make any individual command arguments part of the constructor parameters. The commands themselves need to have a uniform public interface, but for the constructors, where the specific type of command is still known, this restriction does not exists.

Now, there are cases where parameters are only known at the time of command execution, not at construction time, and I guess that is what you have in mind. There are different solutions to keep the public interface of the Command's class uniform and still allow different parameters, but it all boils down to how the execute method has to be designed:

class Command
{
public:
    virtual void execute(/* what goes in here? */)=0;
};

Out of my head:

  1. Give execute a string parameter and let the individual commands parse their parameters themselves. This looks like something which could fit well to the described example.

  2. Give execute a parameter of type map<string,string> (with a mapping from a parameter name to the string-encoded value) or vector<string> (with parameters in some order).

    This will allow to implement more of the parsing inside the "command centre", so execute will only have to convert individual parameters from strings to the desired type.

  3. Use a general CommandParameters superclass, with subclasses TurnOnParameters, PumpParameters etc. At the same place where your comand centre decides about which specific command object has to be taken, it also takes a corresponding parameter object. Let the parameter object do the parsing.

  4. Command objects may provide some string-encoded metadata about the parameters and the types they expect (for example, a comma separated list of types, or something similar to a C/C++ function signature, written as a string). So once the correct command type has been identified, the command parser can ask the command objects for that meta description and use it for the parsing process. The result of the parsing still needs to be something like what I suggested under points 2 or 3.

    This is more effort, but it will allow to implement more of the error handling code directly in the parameter parser. I would only choose it if lots of commands are required, and the parser development and the command development are in the responsibility of different persons or teams.

There are surely more ways, some of them may utilize functional approaches, but I think you get the general idea: to keep the interface uniform, one needs to keep the parameter passing uniform.

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .