A little background may be needed, but skip to Problem if you feel confident. Hopefully the summary gets the point across.
Summary
I have an InputDispatcher
which dispatches events (mouse, keyboard, etc...) to a Game
object.
I want to scale InputDispatcher
independently of Game
: InputDispatcher
should be able to support more events types, but Game
should not be forced to use all of them.
Background
This project uses JSFML.
Input events are handled through the Window
class via pollEvents() : List<Event>
. You must do the dispatching yourself.
I created a GameInputDispatcher
class to decouple event handling from things such as handling the window's frame.
Game game = ...;
GameInputDispatcher inputDispatcher = new GameInputDispatcher(game);
GameWindow window = new GameWindow(game);
//loop....
inputDispatcher.dispatch(window::pollEvents, window::close);
game.update();
window.render();
The loop has been simplified for this example
class GameInputDispatcher {
private Game game;
public GameInputDispatcher(Game game) {
this.game = game;
}
public void dispatch(List<Event> events, Runnable onClose) {
events.forEach(event -> {
switch(event.type) {
case CLOSE: //Event.Type.CLOSE
onClose.run();
break;
default:
// !! where I want to dispatch events to Game !!
break;
}
}
}
}
The Problem
In the code directly above (GameInputDispatcher
), I could dispatch events to Game
by creating Game#onEvent(Event)
and calling game.onEvent(event)
in the default case.
But that would force Game
to write the implementation for sorting & dispatching mouse and keyboard events:
class DemoGame implements Game {
public void onEvent(Event event) {
// what kind of event?
}
}
Question
If I wanted to feed events from InputDispacher
into Game
, how could I do so while avoiding Interface Segregation Principle violations? (by declaring all listening methods: onKeyPressed,
onMouseMoved, etc.. inside of
Game`, even though they may not be used).
Game
should be able to choose the form of input it wants to use. The supported input types (such as mouse, key, joystick, ...) should be scaled through InputDispatcher
, but Game
should not be forced to support all these inputs.
My Attempt
I created:
interface InputListener {
void registerUsing(ListenerRegistrar registrar);
}
Game
would extend this interface, allowing InputDispatcher
to depend on InputListener
and call the registerUsing
method:
interface Game extends InputListener { }
class InputDispatcher {
private MouseListener mouseListener;
private KeyListener keyListener;
public InputDispatcher(InputListener listener) {
ListenerRegistrar registrar = new ListenerRegistrar();
listener.registerUsing(registrar);
mouseListener = registrar.getMouseListener();
keyListener = registrar.getKeyListener();
}
public void dispatch(List<Event> events, Runnable onClose) {
events.forEach(event -> {
switch(event.type) {
case CLOSE:
onClose.run();
break;
case KEY_PRESSED:
keyListener.onKeyPressed(event.asKeyEvent().key);
break;
//...
}
});
}
}
Game
subtypes can now implement whatever listener is supported, then register itself:
class DemoGame implements Game, MouseListener {
public void onKeyPressed(Keyboard.Key key) {
}
public void registerUsing(ListenerRegistrar registrar) {
registrar.registerKeyListener(this);
//...
}
}
Attempt Issues
Although this allows Game
subtypes to only implement the behaviors they want, it forces any Game
to declare registerUsing
, even if they don't implement any listeners.
This could be fixed by making registerUsing
a default
method, having all listeners extend InputListener
to redeclare the method:
interface InputListener {
default void registerUsing(ListenerRegistrar registrar) { }
}
interface MouseListener extends InputListener {
void registerUsing(ListenerRegistrar registrar);
//...listening methods
}
But this would be quite tedious to do for every listener I choose to create, violating DRY.