Because of type erasure, the handle
method doesn't actually know what type you have passed into the RESULT
type parameter, and therefore cannot select a handler based on the result type. It doesn't know the COMMAND
type parameter either, but you can kind of get it by command.getClass()
. This wouldn't work either, if the command can be null, or if you pass a more derived class than what you passed to the COMMAND
type parameter.
All that is to say, the caller has to pass a Class
, to indicate the class of the result, and if command.getClass()
isn't enough, the class of the command.
Now onto how you would retrieve the handlers. Assuming all your handlers implement the interface directly and supplies actual classes for the COMMAND
and RESULT
type parameters, it is possible to get the correct handler from a list of Handler<?, ?>
using getActualTypeArguments
like this.
for (var handler : handlers) {
var typeArguments = ((ParameterizedType)handler.getClass().getGenericInterfaces()[0]).getActualTypeArguments()
if (typeArguments[0] == command.getClass() && typeArguments[1] == resultClass) {
// this handler is correct!
}
}
That said, I prefer using a Map<Pair<Class<?>, Class<?>>, Handler<?,?>>
to store the handlers, essentially storing the COMMAND
and RESULT
information as Class
es in the map's keys.
private final Map<Pair<Class<?>, Class<?>>, Handler<?, ?>> handlers = new HashMap<>();
// always use this to add handlers
private <COMMAND, RESULT> void registerHandler(
Class<COMMAND> commandClass,
Class<RESULT> resultClass,
Handler<? super COMMAND, ? extends RESULT> handler) {
handlers.put(new Pair<>(commandClass, resultClass), handler);
}
Then handle
can be implemented like this:
@SuppressWarnings("unchecked")
// optionally, add a Class<COMMAND> parameter, instead of using command.getClass to get the command class
public <COMMAND, RESULT> RESULT handle(COMMAND command, Class<RESULT> resultClass) {
var handler = (Handler<COMMAND, RESULT>)handlers.get(
new Pair<Class<?>, Class<?>>(command.getClass(), resultClass)
);
if (handler == null) {
throw new HandlerNotFoundException(...);
}
return handler.handle(command);
}
Note that since this uses Class
es, it does not distinguish between e.g. List<Integer>
and List<String>
. If you want to distinguish between those, the caller needs to pass Type
s (which involves this trick with a helper class). And using a map probably wouldn't be as useful. You'd just go through the list and compare the Type
s manually.