2017-02-03 5 views
3

Может понадобиться небольшой фон, но пропустите Задача если вы чувствуете себя уверенно. Надеемся, что итоговое резюме будет иметь смысл.Как отделить модуль, управляемый событиями?

Резюме

У меня есть 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 должен иметь возможность выбирать способ ввода, который он хочет использовать. Поддерживаемые типы ввода (такие как мышь, клавиша, джойстик, ...) следует масштабировать с помощью 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.

ответ

1

Я не вижу смысла в registerUsing(ListenerRegistrar). Если необходимо написать код, открытый для слушателя, который знает, что это слушатель, и поэтому ему необходимо зарегистрироваться с помощью ListenerRegistrar, тогда он может также пойти и зарегистрировать слушателя у регистратора.

Проблема, как указано в вашем вопросе, обычно обрабатывается в графических интерфейсах с помощью обработки по умолчанию, используя либо наследование, либо делегирование.

С наследованием, вы бы базовый класс, назовем его DefaultEventListener или BaseEventListener, что вы предпочитаете, который имеет public void onEvent(Event event) метод, который содержит переключатель заявление, в котором проверяет тип события и вызывает переопределение на себя для каждого события, он знает. Эти переопределения обычно ничего не делают. Тогда ваша «игра» происходит от этого DefaultEventListener и обеспечивает переопределяющие реализации только для событий, которые она волнует.

С делегацией вас есть switch заявления, в котором вы проверяете для событий, которые вы знаете о, и в предложении о default вашей switch делегировать какое-то defaultEventListener типа DefaultEventListener, который, вероятно, ничего не делает.

Существует вариация, которая сочетает в себе: слушатели событий возвращают true, если они обрабатывают события, в этом случае switch оператор немедленно возвращает так, что событие не будет обрабатываться дальше, или false, если они не обрабатывают события , в этом случае оператор switchbreak s, поэтому код в конце инструкции switch принимает управление, и то, что он делает, - пересылка события другому слушателю.

Альтернативный подход (используемый во многих случаях в SWT, например) включает регистрацию метода наблюдателя для каждого отдельного события, которое вы можете наблюдать.Если вы это сделаете, обязательно забудьте отменить регистрацию каждого события, когда ваш игровой объект умрет, или же он станет зомби. Приложения, написанные для SWT, полны утечек памяти, вызванных элементами управления gui, которые никогда не собираются с мусором, потому что у них есть какой-то наблюдатель, зарегистрированный где-то, хотя они давно закрыты и забыты. Это также является источником ошибок, потому что такие элементы управления зомби продолжают получать события (например, события клавиатуры) и продолжают пытаться делать что-то в ответ, хотя у них больше нет gui.

+0

'registerUsing' был способом избежать« игры », зная о ее контейнерах (что« InputDispatcher »является одним из них). 'InputListenerRegistrar' отделяет регистрацию от' InputDispatcher'. Это позволяет мне удалять замену 'InputDispatcher' всем, что я хотел бы, поощряя повторное использование. –

+0

Что касается 'DefaultEventListener', я не хочу выполнять больше проверки типов, чем я уже делаю (что я только делаю, потому что JSFML в основном заставляет его). Использование этого метода по-прежнему нарушает ISP, поскольку методы, которые не используются, будут объявлены. Несмотря на то, что 'DemoGame' не должен объявлять об этом, он все еще находится в интерфейсе' DemoGame'. «Ничего не делают» сами методы являются запахами кода. Я мог бы сохранить 'registerUsing' по умолчанию и не обновлять его в' KeyListener' и 'MouseListener', если бы я хотел такого поведения. –

+0

Вот как это делают несколько существующих guis. (Win32, MFC, WinForms, Swing, SWT, gui, которые я построил во всей полноте и т. Д.) Итак, как это или нет, вонючий или нет, так много людей, которые имеют большой опыт в этих дела решили это сделать. И вы знаете, как все думают, что они знают лучше, и они будут делать это по-другому? Ну, в этом случае они обычно не делают этого по-другому. Должна быть причина для этого. Делай то, что будешь с этой информацией. –

0

Повторяя вопрос другу, я считаю, что нашел проблему.

Несмотря на то, что это позволяет подтипам игры только реализовать поведение, которое они хотят, оно заставляет любую Игру объявлять registerUsing, даже если они не реализуют никаких слушателей.

Это говорит о том Game уже нарушение ISP: если клиенты не будут использовать слушателей, Game не следует выводить из InputListener.

Если по какой-то причине, Game подтип не хотел использовать слушатель (возможно взаимодействие осуществляется через веб-страницу или на локальном компьютере), Game не должен быть вынужден объявить registerUsing.

Решение

Вместо этого InteractiveGame может вывести из Game и реализации InputListener:

interface Game { } 
interface InteractiveGame extends Game, InputListener { } 

рамки будут затем проверить тип Game, чтобы увидеть, если это необходимо, чтобы создать экземпляр InputDispatcher:

Game game = ...; 
if(game instanceof InteractiveGame) { 
    // instantiate input module 
} 

Если кто-то может предложить лучшее de знак, пожалуйста, сделайте это. Этот проект был попыткой отделить диспетчеризацию событий от программ, которые хотят использовать пользовательские события, обеспечивая при этом сильную безопасность во время компиляции.

Смежные вопросы