Централизованное распространение событий по схеме наблюдателя - PullRequest
0 голосов
/ 17 декабря 2011

в игре, которую мы запрограммировали в java, мы часто используем наблюдателей. Для распространения событий у нас есть петли типа

for(Observer o : observers) {
   o.somethingHappened();
}

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

for(Observer o : observers) {
   o.somethingElseHappened();
}

Некоторое время назад у меня было то же самое в C ++, и я мог использовать указатель на функцию-член, а затем написать что-то вроде

dispatchEvent(&MyAbstractHandler::somethingHappened); //loop over observers for any "event" provided

.. что кажется мне лучшим решением. Но поскольку Java не способна на это, мне интересно, какое решение лучше.

Вы можете использовать Event-Object и передать его методу handleEvent (), который перегружен для нескольких наследников Event, поэтому используйте полиморфизм. Но это дает вам кучу пустых классов (и я вообще критически отношусь к классам просто ради полиморфизма). Также есть Reflection, но это не похоже на типичный вариант использования, а также есть проблемы с производительностью.

Так что вы думаете по этому поводу? Я пропускаю метод?

Ответы [ 2 ]

4 голосов
/ 17 декабря 2011

(если говорить с точки зрения разработчика MMO)

Я обнаружил, что ради масштабируемости вы можете захотеть обрабатывать обработку событий в более чем одном потоке;У меня обычно был такой шаблон:

 public class Event { EventType getType (); }
 // extend Event for special cases that require additional attributes…

 public class EventDispatcher {
       Map<Class<? extends Event>, List<Observer>> observers;
       void send (final Event e) {
            for (Observer o : observers.get(e.getClass())) {
                  o.receiveEvent (e);
            }
       }
  }

Или вы можете использовать enum:

  public enum EventType { … } ;
  …
  Map <EventType,List<Observer>> observers;
  …
       for (Observer o : observers.get(e.getEventType ()))  …

По мере того, как вы будете справляться с большим количеством наблюдателей, вам может понадобитьсяразбить обработку событий и / или заставить ее запускаться в других потоках.Метод EventDispatcher.send(Event) дает вам одно место для его расширения, например, вставляя пространственные разделы (вы не можете обнаружить это событие, потому что вы слишком далеко), или вставляя объекты Event в очередь для асинхронной обработки в другом потоке.(s), где обработчик (и) событий не будет влиять на поток, генерирующий событие.

Мы первоначально начали использовать эту модель для обработки распространения событий обратно к игрокам (так что сетевой код выполнялся синхронно насвоего собственного потока), но обнаружил, что система очередей помогла ускорить распространение ИИ и физики.

Недостатком, возможно, является то, что вам необходимо иметь фильтр switch / case в классе Observer (es).).Как правило, это относится к общему базовому классу, если предположить, что у вас есть достаточно небольшое число типов EventTypes для перечисления.

На более ранних итерациях ядра Romance MMO (и все еще поддерживается в базе кода 1.1 для обратной совместимости)Мы использовали интерфейсы для получения различных событий.(Базовый класс подключает их к общим получателям событий - которые мы называем действиями, чтобы отличать их от обработчиков событий пользовательского интерфейса.)

 public interface EventFooHandler {
         public void handleEventFoo (int bar, int baz);
 };
 public class SomeObject implements EventFooHandler …

Возможно, эти интерфейсы были тем, что вы имели в виду под «тонной пустых классов?». Этокак правило, «разговорный Java правильный способ сделать это» для вещей, которые работают в вашем собственном стеке / потоке;Например, я считаю, что Swing широко использует «функции обратного вызова» таким же образом, как и любое количество других стандартных библиотек Java.

Конечно, если вы не хотите просто реализовывать интерфейсы на каждом Observer, выможно использовать анонимные классы…

    getEventDispatcher ().registerForEventFoo 
        ( new EventFooHandler {
               public void handleEventFoo (int bar, int baz) {
                      this.doGribble (bar + 2, baz);
             }});

Если вы не кусаете пулю и не погружаетесь в системы Entity (я уверен, что кто-то обязательно заскочит и подключит один, возможно, упомянув радости неизменного состояния и50% шансов, что они рекомендуют перейти на Erlang для вашего следующего проекта :-)), я бы склонялся к чему-то похожему на первый.Система «единообразного события» позволяет «ортогонально» быть ортогональной, маршрутизация события → наблюдатель может быть изменена в одном месте и предотвращает распространение разных методов на наблюдателей для каждого типа события, с которым вы сталкиваетесь с течением времени.

1 голос
/ 17 декабря 2011

В Java у вас есть прослушиватели событий, которые могут запускаться, когда им нужно, например, у меня была программа, которая должна была обновлять представление Order каждый раз, когда был изменен заказ. Поэтому я настроил PropertyChangeListener так, чтобы всякий раз, когда определенные свойства были изменены (например, TransactionsList), он запускал метод для обновления JTable заказа.

Вы должны быть осторожны при их использовании, чтобы избежать проблем с параллелизмом.

Вот пример кода:

public Order makeNewOrder() {
    currentOrder = new Order();
    currentOrder.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
        public void propertyChange(java.beans.PropertyChangeEvent evt) {
            updateOrderTable();
        }
    });
    updateOrderTable();
    return currentOrder;
}

выдержки из класса заказа:

public class Order implements Serializable {
...
transient private PropertyChangeSupport propertyChangeSupport
    = new PropertyChangeSupport(this);
...
    public void addTL(...) {
    TransactionLine t = new TransactionLine(...);
    transactions.add(t);
    if (propertyChangeSupport != null)
        propertyChangeSupport.firePropertyChange("transactions", false, true);
    }
//each instance should add this when created
public void addPropertyChangeListener(PropertyChangeListener listener) {
    propertyChangeSupport.addPropertyChangeListener(listener);
}

Это метод, который называется:

/** This method is fired on propertyChange */
public void updateOrderTable() {
    OrderTable ot = new OrderTable(currentOrder);
    currentOrderTable.setModel(ot.getTableModel());
    currentOrderTable.setColumnModel(ot.getColumnModel());
    savedOrdersSelector.setModel(new javax.swing.DefaultComboBoxModel(getSavedOrders()));
    OrderSummary.setOrder(currentOrder);
}
...