Слушатель событий и издатель

Это моя первая попытка создания системы событий.

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

Для этого я создал базовые классы для общедоступного издателя /слушателя

abstract class AEventPublisher<Model, Publisher extends AEventPublisher<Model, ?>> {
    private List<IEventListener<Model, Publisher>> listeners;

    public AEventPublisher(){
        listeners = new ArrayList<IEventListener<Model, Publisher>>();
    }

    public void subscribe(IEventListener<Model, Publisher> listener) {
        listeners.add(listener);
    }

    public void unsubscribe(IEventListener<Model, Publisher> listener) {
        listeners.remove(listener);
    }

    protected void publish(Publisher publisher, Model sender) {
        for(IEventListener<Model, Publisher> listener : listeners){
            listener.actionPerformed(publisher, sender);
        }
    }

    public abstract void publish(Model sender);
}

interface IEventListener<Model, EventPublisher extends AEventPublisher<Model, ?>> {
    public void actionPerformed(EventPublisher publisher, Model sender);
}

Метод публикации был сделан защищенным и абстрактным, потому что я не мог придумать способ получить this в AEventPublisher, который будет отображаться как тип Publisher extends AEventPublisher<Model, ?>


Некоторые определенные события /слушатели будут тогда:

public class GameEvents{
    public static interface IButtonEventListener extends IEventListener<MenuButton, ButtonEventPublisher>{}

    public static class ButtonEventPublisher extends AEventPublisher<MenuButton, ButtonEventPublisher> {
        @Override
        public void publish(MenuButton sender) {
            publish(this, sender);
        }
    }

    public static interface IJoystickEventListener extends IEventListener<Joystick, JoystickEventPublisher> {}

    public static class JoystickEventPublisher extends AEventPublisher<Joystick, JoystickEventPublisher> {
        @Override
        public void publish(Joystick sender) {
            publish(this, sender);
        }
    }
}

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

Это будет пример использования как часть конструктора для моего класса Player.

joystickCallback = new IJoystickEventListener(){
    @Override 
    public void actionPerformed(JoystickEventPublisher event, Joystick sender) 
    {
        updatePlayerDirection(sender);
    }
};
joystick.getJoystickEventPublisher().subscribe(joystickCallback);
//Then before player is deleted, this would be called:
//joystick.getJoystickEventPublisher().unsubscribe(joystickCallback);
11 голосов | спросил flakes 13 MarpmFri, 13 Mar 2015 22:08:45 +03002015-03-13T22:08:45+03:0010 2015, 22:08:45

2 ответа


12

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

Имена и дизайн

Кажется, что ваши имена классов (и интерфейсов) исходят из явной перспективы разработки C #. Префикс I для интерфейсов обоняет .NET мне. Всякий раз, когда я занимаюсь разработкой Java, я стараюсь поддерживать интерфейсы без помех, а затем обычно суффикс классов реализации, если это необходимо.

Следующая вещь, которая немного меня заинтересовала, - это префикс A для вашего абстрактного класса. Я вообще не большой друг абстрактных классов. Я стараюсь избегать их, где только могу, и предпочитаю интерфейсы и «Стандартные [...]» - реализации. Мне было лучше с этим, так как абстрактные классы часто ограничивают использование «Интерфейса» слишком много.

Я бы предпочел написать небольшой дубликат кода, чем создать центральный абстрактный класс, который ограничивает мои возможности при использовании интерфейса. Вместо этого я создам простой, если не «минималистический» интерфейс. Несколько минималистических интерфейсов можно комбинировать, чтобы обеспечить более сложное использование, но так как ваш прецедент ясен ....

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

Анализ кода:

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

Следующее, что я увидел, это ваша невероятно длинная спецификация дженериков:

AEventPublisher<Model, Publisher extends AEventPublisher<Model, ?>>

Мне кажется, что для меня много ненужной работы. Кроме того, вы бесполезно ограничиваете возможности, которые у вас есть, при использовании механизма публикации. ИМО было бы достаточно:

AEventPublisher<Model, P extends Publisher>

Затем я увидел, как вы храните listeners. У вас есть private List<...>

У вас есть два варианта при создании абстрактного класса, подобного этому. Либо вы разрешаете «полный» доступ к вашим полям (делая их protected), либо полностью закрываете себя.

Следуя принципам Open /Closed (Open to extension , закрыто до модификация ). Я определенно предлагаю последнее. Вы сделали это не так уж плохо, но у меня есть два маленьких нитпика.

  1. Сделайте свои «неизменные» поля действительно неизменяемыми:

    private final List<...>
    

    лучше, чем просто закрытый список.

  2. Используйте «общие» языковые функции:
    Вы в настоящее время инициализируете свой список в конструкторе и (снова) повторяете определение неуклюжих дженериков. Сохраните это и используйте функцию Java 7:

ответил Vogel612 16 MarpmMon, 16 Mar 2015 22:43:26 +03002015-03-16T22:43:26+03:0010 2015, 22:43:26
4

Vogel612 покрыл большую часть точек стиля, а также очень подходящую задачу «это стоит делать».

Но если вы собираетесь это сделать ...

Важной идеей для понимания этого кода является то, что у вас есть две очень разные проблемы, встроенные в AEventPublisher. Первый из них - издатель; есть также менеджер EventListeners. Все будет намного яснее, если мы представим эти абстракции

public interface Publisher<E> {
    public void publish(E sender);
}

public interface Observable<L> {
    void subscribe(L listener);
    void unsubscribe(L listener);
}

Обратите внимание, что эти дженерики не ограничены - понятия, которые мы представляем здесь, являются общими, поэтому типы должны это выражать. Сложность, характерная для этого варианта использования, будет представлена ​​в классах, реализующих прецедент.

Говоря о том, что

interface EventListener<E, P> {
    void actionPerformed(P publisher, E event);
}

Мы упрощаем EventListener.

В качестве побочного примечания я не слишком увлекаюсь actionPerformed. «Выполненное действие» является отличным (оправданием неопределенности) имени для события, но это неверно для метода объекта. Я бы предпочел либо onEvent - для общего обработчика - или onEventHappened. Тот факт, что мы ожидаем дженериков, делает хорошее именование более жестким; но я думаю, что onActionPerformed будет более соответствовать лучшим практикам.

В этот момент у нас есть интерфейсы, которые нам нужно сделать что-то страшное ....

abstract static class EventPublisher
        < E
        , P extends EventPublisher<E,P>>
        implements
        Publisher<E>
        , Observable<EventListener<E,P>> {
    List<EventListener<E,P>> listeners;

    public void subscribe(EventListener<E,P> listener) {
        listeners.add(listener);
    }

    public void unsubscribe(EventListener<E,P> listener) {
        listeners.remove(listener);
    }

    @Override
    public void publish(E event) {
        for(EventListener<E,P> listener : listeners){
            listener.actionPerformed(self(),event);
        }
    }

    // NOTE: this should only be implement in final classes!
    abstract protected P self();

}

Отказ от ответственности: безумное форматирование для удобочитаемости - это запах кода - хороший обзор кода должен бросить вызов вам hard на то, что это вводит ненужную сложность.

Так что это такое? Разбивая его, мы определяем класс с двумя родовыми параметрами. E - событие, которое может быть любым; P ... ну, это что-то, что расширяет EventPublisher, который мы сейчас определяем. Класс также обещает реализовать хороший чистый Publisher и Observable, которые мы определили ранее.

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

Почему мы делаем эту сумасшедшую вещь?

  

Метод публикации был сделан защищенным и абстрактным, потому что я не мог думать о том, как это сделать в AEventPublisher, чтобы быть отличным, поскольку Publisher расширяет AEventPublisher

Вправо - мы просто не можем использовать this в базовом классе и сохранять специализированное поведение, которое мы хотим. Мы побили с помощью, по-видимому, самореферентного параметра P в подписи - наш специализированный EventPublisher собирается предоставить реализацию self() с точной подписями, которые нам нужны для ---- +: = 17 =: + ----, чтобы сделать правильную вещь.

Давайте попробуем пример - я хотел, чтобы моя IDE показывала мне, что все работает, поэтому сначала я создал некоторые реализации, которые будут использоваться в специализированной специализации.

EventListeners

Этого достаточно, чтобы доказать, что interface Joystick { int getDirection(); } static class Game { static void updatePlayerDirection(Joystick joystick) { joystick.getDirection(); } } действительно обрабатывает правильный тип события. Но я также хочу видеть, что он действительно может отличить правильный тип EventHandler, поэтому я добавляю дополнительный интерфейс, который EventPublisher.

JoystickEventPublisher

Понятно, что здесь я мало работаю над творчеством.

ОК, showtime - как выглядит код interface BobNotifier { void notifyBob(); } ?

JoystickEventPublisher

final static class JoystickEventPublisher extends EventPublisher<Joystick, JoystickEventPublisher> implements BobNotifier { @Override public void notifyBob() { //... } // We're "allowed" to implement self(), because this class is final. protected JoystickEventPublisher self() { return this; } } extends JoystickEventPublisher, что означает это действительно P, который расширяет EventPublisher<E,JoystickEventPublisher<E,P>>, и компилятор удовлетворен!

A EventPublisher<E,P>, который подписывается на JoystickEventListenerдействительно использует методы, которые не являются частью интерфейса JoystickEventPublisher:

EventPublisher

И как подписка, так и публикация «просто работают».

static class JoystickEventListener implements EventListener<Joystick, JoystickEventPublisher> {

    public void actionPerformed(JoystickEventPublisher publisher, Joystick joystick)
    {
        publisher.notifyBob();
        Game.updatePlayerDirection(joystick);
    }
};

Несколько дальнейших пунктов. Шаблон абстрактного self () появляется в других контекстах - он очень полезен для внедрения беглых строителей. Таким образом, вы можете подумать о том, чтобы дразнить эту штуку.

    JoystickEventPublisher p = new JoystickEventPublisher();
    p.subscribe(new JoystickEventListener());
    p.publish(new Joystick() {
        @Override
        public int getDirection() {
            return 0;
        }
    });

Это красиво, чисто, доступно для повторного использования ... Он отлично работает, когда вы строите новый класс с нуля. Однако, если вы пытаетесь расширить какой-либо другой класс, язык не позволит вам продлить второй класс, и вам придется переписать код abstract static class AbstractSelf<T extends AbstractSelf<T>> { // NOTE: this should only be implement in final classes! abstract protected T self(); } abstract static class EventPublisher < E , P extends EventPublisher<E,P>> extends AbstractSelf<P> implements Publisher<E> , Observable<EventListener<E,P>> { //... } в любом случае. Это может не стоить усилий.

Вторые - вы можете не захотеть ограничить типы self() прикреплены к Listeners. Например, у вас может быть общий приемник, который хочет подписаться на события джойстика и события клавиатуры. Мы можем поддержать это здесь, ослабив ограничения для слушателей. Это изменит часть интерфейса EventPublishers, оставив все остальное в покое - еще один намек на наблюдение и публикацию - две отдельные проблемы.

Observer

Компилятор достаточно доволен ....

abstract static class EventPublisher
        < E
        , P extends EventPublisher<E,P>>
        implements
        Publisher<E>
        , Observable<EventListener<? super E, ? super P>> {
    List<EventListener<? super E, ? super P>> listeners;

    public void subscribe(EventListener<? super E, ? super P> listener) {
        listeners.add(listener);
    }

    public void unsubscribe(EventListener<? super E, ? super P> listener) {
        listeners.remove(listener);
    }

    @Override
    public void publish(E event) {
        for(EventListener<? super E, ? super P> listener : listeners){
            listener.actionPerformed(self(),event);
        }
    }

    // NOTE: this should only be implement in final classes!
    abstract protected P self();

}

Наконец, вы можете обнаружить, что хотите расширить p.subscribe(new EventListener<Object, Object>() { @Override public void actionPerformed(Object publisher, Object event) { //... } }); . В вышеприведенной реализации класс был намеренно объявлен окончательным, чтобы закрыть эту дверь, потому что определение JoystickEventPublisher предоставлено self() предотвратит доступ слушателей событий к любым новым методам в JoystickEventPublisher. Правильный ответ состоит в том, чтобы реорганизовать ExtendedJoystickEventPublisher на две части - абстрактную и расширяемую часть, содержащую все интересные части интерфейса, и конечная конкретная деталь, которая реализует JoystickEventPublisher

self()

Эта реализация позволяет нам создать вид abstract static class AbstractJoystickEventPublisher<P extends AbstractJoystickEventPublisher<P>> extends EventPublisher<Joystick, P> implements BobNotifier { @Override public void notifyBob() { //... } } final static class JoystickEventPublisher extends AbstractJoystickEventPublisher<JoystickEventPublisher> { // We're "allowed" to implement self(), because this class is final. protected JoystickEventPublisher self() { return this; } } , который разделяет реализацию JoystickEventPublisher в дополнение к предоставлению новых методов ....

BobNotifier
ответил VoiceOfUnreason 17 MarpmTue, 17 Mar 2015 23:28:55 +03002015-03-17T23:28:55+03:0011 2015, 23:28:55

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132