Reputation: 39
I am writing an application that executes certain functions depending on user input. E.g. if the user input were to be "1 2 add" the output would be "3". I aim to implement many such methods (div, modulo, etc.). As my Scanner recognizes a function name like "add" the function "add()" should be called.
My way to do this is to let a FunctionHandler class evaluate the input.
Main:
String inputCommand = sc.nextCommand();
functionHandler.handle(inputCommand);
Function Handler:
public class FunctionHandler {
public void handle (String functionName) {
if (functionName.equals("add")) {
add();
} else if (functionName.equals("div") {
div();
}
}
private void add() {
.......
}
....
}
As I am adding more and more functions the if statement gets very large, and of course the FunctionHandler class too. Also, whenever I add a new function, I have to change code in two places: I have to define the function, and then add the else if clause in handle() to call the function. Which means two pieces of information that should be encapsulated are "stored" completely independent from each other. I was wondering what the best practice was to solve this kind of situation?
I was thinking about using enums, but they don't seem to fit well in this case.
Another idea I had was creating an interface Function, and then a class for each function that implements Function. The interface would have two methods:
Then I could create an array (manually) of Functions in the FunctionHandler, through which I could loop to see if the command the user enters matches getName(). However, having a different class for each function is not very clean either, and it also does not get rid of the problem that for each function I am adding I have to do it in two places: the class and the array.
This question is only about finding out how to solve this problem cleanly. A pointer in the right direction would be appreciated!
Thanks a lot!
Upvotes: 2
Views: 2988
Reputation: 13807
I would do something like this:
public class FunctionTest {
private static final Map<String, Runnable> FUNCTIONS = new HashMap<String, Runnable>() {{
put("add", () -> System.out.println("I'm adding something!"));
put("div", () -> System.out.println("I'm dividing something!"));
}};
public void handle(String functionName) {
if (!FUNCTIONS.containsKey(functionName)) {
throw new IllegalArgumentException("No function with this name: " + functionName);
}
FUNCTIONS.get(functionName).run();
}
}
You basically can use any functional interface in place of Runnable
, I used it, because it matches your add()
method. You can map the names of the functions to their actual executable instance, get them by name from the Map
and execute them.
You could also create an enum
with the desired executable blocks:
public class FunctionsAsEnumsTest {
private static enum MyFunction {
ADD {
@Override public void execute() {
System.out.println("I'm adding something");
}
},
DIV {
@Override public void execute() {
System.out.println("I'm dividing something");
}
};
public abstract void execute();
}
public void handle(String functionName) {
// #toUpperCase() might not be the best idea,
// you could name your enums as you would the methods.
MyFunction fn = MyFunction.valueOf(functionName.toUpperCase());
fn.execute();
}
}
Upvotes: 0
Reputation: 805
Another option would be to keep a Map
of handlers. If you're using Java 8, they can even be method references.
// InputType and ResultType are types you define
Map<String, Function<InputType, ResultType>> operations = new HashMap<>();
operations.put("add", MathClass::add);
// ...
ResultType result = operations.get(userInput).apply(inputObject);
One downside to doing it this way is that your input and output types must be the same for all operations.
Upvotes: 2
Reputation: 4945
You could create a custom annotation for the various functions. Then you could employ your array idea, but have it use reflection to discover which functions have your new annotation and what their names are.
As background, take a look at http://www.oracle.com/technetwork/articles/hunter-meta-2-098036.html and http://www.oracle.com/technetwork/articles/hunter-meta-3-092019.html. They're a bit old, but seem to address the necessary ideas.
Upvotes: 1
Reputation: 1542
Assuming you do not have a lot of functions that you want to do this way, and do not want to expose yourself to the security risks caused by reflection, you could use a string switch, like this:
void handleFunction(String function) {
switch (function) {
case "foo":
foo();
break;
case "bar":
bar();
break;
default:
throw new IllegalArgumentException("Unknown function " + function);
break;
}
}
Starting Java 7, you can use Strings in a switch statement and the compiler will make something reasonable out of it
Upvotes: 0
Reputation: 12393
You can always use reflection if you want a short solution.
In your handle
method you could do something like this:
Method m = this.getClass().getMethod(functionName, new Class[]{});
m.invoke(this, new Object[]{});
Upvotes: 0