In my current software engineering course, my team is working on a library management system that is essentially a command-line/REPL environment with a half dozen commands, e.g. checkout
, search
, etc. We've elected to use the command pattern for this, so each command will be represented as a class. Now, there's the question of how we create new command objects when the user enters a string.
Use conditionals:
switch(input) { case "checkout": return new CheckoutCommand(args); break; // etc.
Create a HashMap mapping strings to constructors:
interface CommandCreator { abstract public Command createCommand(String[] args); }
Map<String, CommandCreator> commandMap = new HashMap<>(); commandMap.put("checkout", CheckoutCommand::new); commandMap.put("search", SearchCommand::new); // etc.
return commandMap.get(input).createCommand(args);
Use reflection:
Class.forName("library.commands." + input).getConstructor(args.getClass()).newInstance(args);
The command classes would then have the same names as the strings that invoke them, i.e.
class checkout implements Command { // class body }
Something else.
We agreed that #1 is not the best solution. Some of us think that #2 should not be used, because it violates the Open/Closed Principle; when we add more commands in the future, we would have to modify the class that populates the command HashMap. Some of use think #3 should not be used, because reflection should only be used as a last resort, and it seems too "hacky". However, none of us have any better ideas.
Should we use #2 or #3, or is there a better solution?
commandDispatcher.addCommand("command37", Command37::new)
.break
. #2 solves that problem. Not a big deal if you're returning from the conditional however. There are situations where the Dictionary is better, but they're equivalent here.