How to model an increasing amount of Commands with

2019-05-31 18:09发布

问题:

This is a follow-up thread on How to get rid of instanceof in this Builder implementation

There are still some problems with this design. Every time a new parameter is introduced, one must create a new ConcereteParameter class.

That's not a problem. But one must also add method in the CommandBuilder append(ConcreteParameter). And I'm not quite liking that dependency.

To summarize

  • Commands can be configured with parameters. Not every command can receive the same parameters. So some have to be ignored. When being applied to a command (In this implementation this is achieved by throwing an UnsupportedOperationException

  • Parameters that can be applied to certain classes are used differently in those classes (Like FTPCommand & HTTPCommand might use IpParameter in a different way)

  • In the future new Commands and Parameters might be introduced

Update The implementation as it is now works. But isn't it overkill that if I have about 30 parameters, that for every parameter I have to have a separate method?

If there is, What is a more clean and more flexible way/pattern to achieve this?

回答1:

What is a parameter for you, and what is a parameter type? If you really have different kinds of objects as parameters, with different operations you may perform on them, then you cannot avoid having different classes to handle them. If your parameters only differ in how the commands interpret them, but otherwise are mostly String and Integer or whatever, then having extra classes for each possible meaning is surely overkill. And if your parameter are some form of key-value pair, then I'd represent them as such: a single class (or perhaps one for each reasonable value type) to contain the name and the value of the parameter.

If you can use the above to reduce the number of parameter types, you might want to consider reflection for the actual command building. You could have an annotation @Parameter which you use to decorate setter methods of your command classes. E.g. @Parameter void setIP(String) would mean that the command accepts a String parameter, and will interpret that as am IP address. If you use key-value parameters, you can either derive the key from the method name, or add a value to the annotation, or both. Using such a framework, you could have a single command builder which would take care of feeding parameters to the appropriate setters.



回答2:

Even though there is an accepted answer, I feel you need to be aware of another option.

I would use a Map as a context object, and pass the context to the execute method of your command. The command will simply pull the parameters it needs out of the Map by String.

public interface Command {
   public void execute(Map<String, Object> context);
}

class OneCommandImpl extends Command {
    public void execute(Map<String, Object> context) {
        context.get('p1');
        context.get('p2');
    }
}

The advantages of this approach are that it's simple, and there is no need for reflection. You can build any command you want, that requires any number of arguments, using this one interface. The primary disadvantage is the type of value in the Map is not specific.