I'm working on an application which has a defined and immutable (for our purposes) communication protocol. One of the features is that users on the controlling terminal can enter text commands that in many ways mimic interacting with a commandline application.
The structure of these commands (wrapped within a normal communication message as defined by this system) is:
<command> -<subcommand> [param] [param] [param]..
For example:
sys -messagedelay 123
Given that the commands act on the behavior of the communication manager itself (the internals of which I don't want to expose outside of that class) which of the following is the better approach?
Using a nested switch:
private SendReceiveResult HandleCommand(string command)
{
string[] splitCommand = command.Split(CommandSplitChars);
switch (splitCommand[0])
{
case "sys":
if (splitCommand.Length < 2)
{
HandleError("Invalid sys command");
break;
}
switch (splitCommand[1])
{
case "-messagedelay":
if (splitCommand.Length < 2 ||
!float.TryParse(splitCommand[1], out messagedelay))
{
HandleError("Missing or invalid messagedelay parameter");
}
break;
// .. more cases
}
break;
// .. more cases
}
}
Using a private parser class, with delegates per command:
// Private nested command parser class
private class CommandParser : IEqualityComparer<string[]>
{
private Dictionary<string[], Func<string[], SendReceiveResult>> commandHandlers;
private Func<string[], SendReceiveResult> defaultHandler;
public CommandParser(Func<string[], SendReceiveResult> defaultHandler)
{
commandHandlers = new Dictionary<string[], Func<string[], SendReceiveResult>>(this)
this.defaultHandler = defaultHandler;
}
public void AddCommandHandler(Func<string[], SendReceiveResult> commandHandler, params string[] pattern)
{
commandHandlers[pattern] = commandHandler;
}
SendReceiveResult ParseCommand(string command)
{
if (string.IsNullOrEmpty(command)) return SendReceiveResult.Success;
string lowerCommand = command.ToLower();
string[] splitCommand = lowerCommand.Split(CommandSplitChars);
Func<string[], SendReceiveResult> commandHandler;
if (commandHandlers.TryGetValue(splitCommand, out commandHandler))
{
return commandHandler(splitCommand);
}
return defaultHandler();
}
public bool Equals(string[] x, string[] y)
{
return y.Length > 2 &&
y[0] == x[0] &&
y[1] == x[1];
}
public int GetHashCode(string[] obj)
{
return obj.GetHashCode();
}
}
// Set up of parser in enclosing class
private void SetupCommandParser()
{
parser = new CommandParser(
(command) =>
{
Log(unsupportedMessage);
return SendReceiveResult.Success;
});
parser.AddCommandHandler(
(command) =>
{
if (command.Length < 2 ||
!float.TryParse(command[1], out messagedelay))
{
HandleError("Missing or invalid messagedelay parameter");
}
return SendReceiveResult.Success;
},
"sys", "-messagedelay");
// Add more commands, but in real implementation use named methods for readability
}
// Use of parser
private SendReceiveResult HandleCommand(string command)
{
return parser.ParseCommand(command);
}
The nested switches just generally have a nasty smell about them, and while the single case is readable the real implementation that would have tens of cases would be far less so. Extending it feels like it could potentially introduce bugs easily.
The parser feels like a more complete and extensible option-and might be something I can pull out later and generalize even more to reuse. At the same time though, it feels like overkill for the task at hand.
Assuming the parser does turn out to be the better option, it could probably use regexes instead of the string array patterns, I just don't know the Regex lib well enough to slap together a quick example like this without some lookup ;)