Skip to content

Instantly share code, notes, and snippets.

@rzwitserloot
Created January 18, 2020 20:34
Show Gist options
  • Save rzwitserloot/9ced180f1ba8b301b796534905b465b3 to your computer and use it in GitHub Desktop.
Save rzwitserloot/9ced180f1ba8b301b796534905b465b3 to your computer and use it in GitHub Desktop.
public abstract class Command {}
public final class ExitCommand extends Command {}
public final class SayHelloCommand extends Command {}
@FunctionalInterface public interface CmdHandler<C extends Command> {
void onCommand(C command); // should probably have 'throws Exception' on it; most entrypoints do.
}
public class CmdRouter {
private final Map<Class<?>, CmdHandler<?>> handlers = new HashMap<>();
public <C extends Command> void register(Class<C> commandType, CmdHandler<C> handler) {
handlers.put(commandType, handler);
}
public void handle(Command command) {
// Simple way to do it; this breaks if you have a hierarchy of commands, but works fine if you don't.
CmdHandler<?> handler = CmdHandlerhandlers.get(command.getClass());
handle(handler, command);
}
public void advancedHandle(Command command) {
// This is how to deal with hierarchies. I'd go with this one.
Class<?> t = command.getClass();
CmdHandler<?> ch = null;
while (ch == null && t != null && t != Command.class) {
ch = handlers.get(t);
t = t.getSuperclass();
}
handle(ch, command);
}
@SuppressWarnings("unchecked")
private void handle(CmdHandler<?> handler, Command command) {
// No way to make the generics not complain, so we suppresswarnings it, and keep the method short.
if (handler == null) throw new IllegalStateException("Handler for " + handler.getClass() + " not registered.");
CmdHandler ch = handler; // dip into raw types to make it work.
ch.onCommand(command);
}
}
and to use:
public class Main {
public static void main(String[] args) {
CmdRouter router = new CmdRouter();
router.register(ExitCommand.class, cmd -> System.exit(0)); // via lambda.
router.register(SayHelloCommand.class, new SayHelloHandler()); // more classical way.
router.advancedHandle(new ExitCommand());
router.advancedHandle(new SayHelloCommand());
}
private static final class SayHelloHandler implements CmdHandler<SayHelloCommand> {
void onCommand(SayHelloCommand command) {
System.out.println("Hello!");
}
}
}
NB: Instead of claling .register a lot, you can use SPI and don't call it at all; then, all you have to do to add another handler, is something like:
@ProviderFor(CmdHandler.class)
public class SayGoodbyeHandler implements CmdHandler<SayGoodbye> {
.....
}
it is possible to create your project such that _all_ you have to do is write the above file, and compile it, and that is it. Your CmdRouter class will completely automatically find it, register it, figure out what types of commands it can handle, and include it in the handlers map. Google for SPI or ask surial in ##java about it :)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment