1

Is there a clean way to delegate a command to the proper handler by matching the generic types of the command and result ?

public interface Handler<COMMAND, RESULT> {

  public RESULT handle(COMMAND command);

}

public class Dispatcher {
  // proper constructor filling the handlers list

  private final List<Handler<?, ?>> handlers = new ArrayList<>();

  public <COMMAND, RESULT> RESULT handle(COMMAND command) {
    Handler<COMMAND, RESULT> handler;
    return handlers.stream()
        .filter(h -> h instanceof Handler<COMMAND, RESULT>)
        .findFirst()
        .get()
        .handle(command);
  }

Build fails stating

Handler<capture of ?, capture of ?>' cannot be safely cast to 'Handler<ENTRY, RESULT>

Is this even possible ? I'm wondering how DI libraries do this as this is "similar" IMO.

Jeep87c
  • 1,050
  • 16
  • 36
  • DI works when there is exactly one matching type, otherwise you need qualifier (at least in CDI). Can an enum CommandType help? If command and handler have the same CommandType it is a match. – Lini Apr 21 '23 at 14:15

2 Answers2

1

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 Classes 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 Classes, it does not distinguish between e.g. List<Integer> and List<String>. If you want to distinguish between those, the caller needs to pass Types (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 Types manually.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
0

Mixing with what @Sweeper suggested and my desire to not pass the RESULT class as a parameter to my method, the only way I see how to achieve this is by adding a reference in the COMMAND to the RESULT as follow:

public interface Command<REQUEST, RESULT> {
    public REQUEST request();
  }

public record Query(UUID id) implements Command<UUID, SomeView> {
    @Override
    public UUID request() {
      return id;
    }
  }

public class Dispatcher {
  private final Map<Pair<Class<?>, Class<?>>, Handler<?, ?>> handlers = new HashMap<>();

  public <REQUEST, RESULT> RESULT handle(Command<REQUEST, RESULT> cmd) {
    // handlers.put(Pair.of(UUID.class, SomeResult.class), someHandler);

    Type[] actualTypeArguments = ((ParameterizedType) cmd.getClass()
        .getGenericInterfaces()[0]).getActualTypeArguments();
    Class<?> a = (Class<?>) actualTypeArguments[0];
    Class<?> b = (Class<?>)  actualTypeArguments[1];
    var handler = (Handler<COMMAND, RESULT>) handlers.get(Pair.of(a, b));

    return handler.handle(cmd);
  }
}

Doing so, the command passed to the handle method has a reference to the RESULT class which we can access through reflection.

Although this is working and fun to know, I would be challenging the usage of it since reflection always come with a performance cost.

Jeep87c
  • 1,050
  • 16
  • 36