If you go with the reflection route, you don't really want to expose your method names to the end users. They might not be end user-friendly, and if they are, there is no reason for users to know this information and there might be methods, which are not supposed to be invoked by users. I would use custom annotations to build more flexible matching.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserChoice {
String userFriendlyOption();
int optionNumber();
}
optionNumber will be used for matching the method to invoke, userFriendlyOption is some user friendly text.
Annotate only the methods, supposed to be used by users.
@RequiredArgsConstructor
public class ProductData {
private final double data;
@UserChoice(userFriendlyOption = "see result for option a", optionNumber = 1)
public void methodA() {
System.out.println(data + 1);
}
@UserChoice(userFriendlyOption = "see result for option b", optionNumber = 2)
public void methodB() {
System.out.println(data + 2);
}
@UserChoice(userFriendlyOption = "see result for option c", optionNumber = 3)
public void methodC() {
System.out.println(data);
}
public void methodNotForUser() {
System.out.println("Should not be seen by users");
}
}
Like this methodNotForUser() can't be invoked by end users.
Simplified matcher might look like this.
@RequiredArgsConstructor
public class ProductTester {
private final ProductData data;
private Map<Integer, MethodData> map;
public void showOptions() {
if (this.map == null) {
this.map = new HashMap<>();
for (Method method : this.data.getClass().getMethods()) {
UserChoice userChoice = method.getAnnotation(UserChoice.class);
if (userChoice != null) {
String userRepresentation = userChoice.optionNumber() + " - " + userChoice.userFriendlyOption();
this.map.put(userChoice.optionNumber(), new MethodData(userRepresentation, method));
}
}
}
this.map.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry -> System.out.println(entry.getValue().getUserRepresentation()));
}
public void showOptionResult(int choice) {
MethodData methodData = this.map.get(choice);
if (methodData == null) {
System.out.println("Invalid choice");
return;
}
System.out.println("Result");
try {
methodData.getMethod().invoke(this.data);
} catch (IllegalAccessException | InvocationTargetException ignore) {
//should not happen
}
}
}
MethodData is simple pojo with the sole purpose to not recalculate user representation.
@RequiredArgsConstructor
@Getter
public class MethodData {
private final String userRepresentation;
private final Method method;
}
Short main to illustrate the idea and play around:
public class Temp {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Write initial value");
double data = scanner.nextDouble();
ProductData myData = new ProductData(data);
ProductTester tester = new ProductTester(myData);
tester.showOptions();
System.out.println("Write option number");
int userChoice = scanner.nextInt();
tester.showOptionResult(userChoice);
}
}