mercredi 28 février 2018

Java - Command Pattern with additional argument parsing

Recently I have really focused on design patterns and implementing them to solve different problems. Today I am working on the Command Pattern.

I have ended up creating an interface:

public interface Command {
    public void execute();
}

I have several concrete implementators:

public class PullCommand implements Command {

    public void execute() {
        // logic
    }
}

and:

public class PushCommand implements Command {

    public void execute() {
        // logic
    }
}

There are several other commands aswell.

Now.. the thing is there's a BlockingQueue<Command> which runs on a different thread using .take() to retrieve queued commands and execute them as they come in (I'd call this Executor class below) by another class which produces them by parsing the user input and using .queue(). So far so good...

The hard part about me is parsing the command (CLI application). I have put all of them in a HashMap:

private HashMap<String, Command> commands = new HashMap<String, Command>();
commands.put("pull", new PullCommand());
commands.put("push", new PushCommand());
//etc..

When user inputs a command, the syntax is such that one thing is for sure, and it is that the "action" (pull / push) comes as first argument, so I can always do commands.get(arguments[0]) and check if that is null, if it is, then the command is invalid, if it isn't, then I have successfully retrieved an object that represents that command. The tricky part is that there are other arguments that also need to be parsed and for each command the algorithm for parsing it, is different... Obviously one thing I can do is put the arguments[] as a parameter to the method execute() and end up having execute(String[] args) but that would mean I have to put the parsing of arguments inside the execute() method of the command, which I would like to avoid for several reasons:

  1. The execution of Command happens on a different thread that uses a BlockingQueue, it executes a single command, then another one etc.. The logic I would like to put inside execute() has to ONLY be the execution of the command itself, without the parsing or any for example heavy tasks which would slow the execution (I do realize parsing several args would not mess up the performance that much.. but here I am learning structural designs and ways to build good coding habits and nice solutions. This would not be perfect by any mean)

  2. It makes me feel like I am breaking some fundamental principles of the "Command" pattern. (Even if not so, I'd like to think of a better way to solve this)

It is obvious that I cannot use the constructor of the concrete commands since HashMap returns already initialized objects. Next thing that comes to mind is using another method inside the object that "processes" (process(String[] args)) the arguments and sets private variables to the result of the parsing and this process(String[] args) method is called by the Producer class before doing queue() on the command, so the parsing would end up OUT of the Executor class (thread) and Point 1. from above would not be a problem.

But there's another problem.. What happens if a user enters a lot of commands to the application, the application does .get(args[0]) on the arguments and retrieves a PullCommand, it uses the process(String[] args) and private variables are set, so the command is queued to the Executor class and it is waiting to be executed. Meanwhile.. another command is input by the user, .get(args[0]) is used again, it retrieves a PullCommand from the HashMap (but that PullCommand is the same as the one that is queued for execution) and process() would be called BEFORE the command has been executed by the Executor class and it would screw up the private variables. We would end up with 2 PullCommands records in the BlockingQueue, second one would be correct from user point of view (since he input what he wants it to do and it does just that), but first one will be the same as the second one (since it is the same object) and it would not correspond to the initial arguments.

Another thing I thought of is using a Factory class that implements the parsing for each of the commands and returns the appropriate Command object. This would mean though, that I need to change the way HashMap is used and instead of Command I have to use the Factory class instead:

HashMap<String, CommandFactory> commands = new HashMap<String, CommandFactory>();
commands.put("pull", new CommandFactory("pull"));
commands.put("pull", new CommandFactory("push"));

and based on the String passed to the Factory, its process() method would use the appropriate parsing for that command and it would return the corresponding Command object, but this would mean that this class could potentially be very big because of containing the parsing for all commands..

Overall, this seems like my only option, but I am very hesistant since from structural point of view, I don't think I am solving this problem nicely. Is this a nice way to deal with this situation? Is there anything I am missing? Is there any way I can maybe refactor part of my code to ease it?

Aucun commentaire:

Enregistrer un commentaire