Может потребоваться небольшая предыстория, но если вы уверены, переходите к Проблеме. Надеюсь, краткое изложение уловит суть.
Резюме
У меня есть InputDispatcher
, который отправляет события (мышь, клавиатура и т. д.) объекту Game
.
Я хочу масштабировать InputDispatcher
независимо от Game
: InputDispatcher
должен иметь возможность поддерживать больше типов событий, но Game
не следует заставлять использовать их все.
Задний план
Этот проект использует JSFML.
Входные события обрабатываются через класс Window
через pollEvents() : List<Event>
. Вы должны сделать рассылку самостоятельно.
Я создал класс GameInputDispatcher
, чтобы отделить обработку событий от таких вещей, как обработка рамки окна.
Game game = ...;
GameInputDispatcher inputDispatcher = new GameInputDispatcher(game);
GameWindow window = new GameWindow(game);
//loop....
inputDispatcher.dispatch(window::pollEvents, window::close);
game.update();
window.render();
Для этого примера цикл упрощен
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;
}
}
}
}
Эта проблема
В приведенном выше коде (GameInputDispatcher
) я мог бы отправлять события в Game
, создавая Game#onEvent(Event)
и вызывая game.onEvent(event)
в случае по умолчанию.
Но это заставит Game
написать реализацию для сортировки и отправки событий мыши и клавиатуры:
class DemoGame implements Game {
public void onEvent(Event event) {
// what kind of event?
}
}
Вопрос
Если бы я хотел передавать события из InputDispacher
в Game
, как я мог бы это сделать, не нарушая принцип разделения интерфейса? (путем объявления всех методов прослушивания: onKeyPressed,
onMouseMoved, etc.. inside of
Game`, даже если они не используются).
Game
должен иметь возможность выбирать форму ввода, которую он хочет использовать. Поддерживаемые типы ввода (такие как мышь, клавиша, джойстик и т. д.) должны быть масштабированы с помощью InputDispatcher
, но Game
не следует заставлять поддерживать все эти вводы.
Моя попытка
Я создал:
interface InputListener {
void registerUsing(ListenerRegistrar registrar);
}
Game
расширит этот интерфейс, позволяя InputDispatcher
зависеть от InputListener
и вызывать метод registerUsing
:
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
теперь могут реализовать любой поддерживаемый слушатель, а затем зарегистрироваться:
class DemoGame implements Game, MouseListener {
public void onKeyPressed(Keyboard.Key key) {
}
public void registerUsing(ListenerRegistrar registrar) {
registrar.registerKeyListener(this);
//...
}
}
Проблемы с попыткой
Хотя это позволяет подтипам Game
реализовывать только то поведение, которое они хотят, это заставляет любые Game
объявлять registerUsing
, даже если они не реализуют никаких слушателей.
Это можно исправить, сделав registerUsing
методом default
, чтобы все слушатели расширяли InputListener
для повторного объявления метода:
interface InputListener {
default void registerUsing(ListenerRegistrar registrar) { }
}
interface MouseListener extends InputListener {
void registerUsing(ListenerRegistrar registrar);
//...listening methods
}
Но это было бы довольно утомительно делать для каждого слушателя, которого я хочу создать, нарушая DRY.