Простая система диспетчеризации сообщений Java - PullRequest
19 голосов
/ 02 июня 2009

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

Но у меня есть несколько вопросов. Во-первых, это кажется очевидной и распространенной проблемой. Есть ли любимые реализации простых систем обмена сообщениями в ВМ? Похоже, что будет.

Во-вторых, и что более важно, я пытаюсь найти достаточно элегантный способ, чтобы диспетчерский класс знал как можно меньше о типах сообщений. Я хотел бы иметь возможность создавать новые виды событий, не изменяя диспетчера сообщений вообще. Тем не менее, у меня есть противоположная проблема. Мне бы очень хотелось, чтобы сигнатуры методов методов обработки были понятны. Другими словами, я бы предпочел следующее:

public class CollisionConsoleHandler implements CollisionListener {
  @Override
  public void spaceshipCollidedWithMeteor( Spaceship spaceship, Meteor meteor ) {
      //...
  }
}

над чем-то более общим и трудным для чтения:

public class CollisionConsoleHandler implements GameMessageListener {
   @Override
   public void handleMessage( GameMessage message ) {
     if( message instanceof SpaceshipCollisionMessage ) {
        Spaceship spaceship = ((SpaeshipCollisionMessage)message).getSpaceship();
        Meteor meteor = ((SpaeshipCollisionMessage)message).getMeteor();
        //...
     }
   }
}

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

Идеи

Ответы [ 3 ]

40 голосов
/ 03 июня 2009

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

Например, общее определение события может быть:

public interface GameEvent<L> {

   public void notify( final L listener);
}

Если ваш CollisionListener:

public interface CollisionListener {

    public void spaceshipCollidedWithMeteor( Spaceship spaceship, Meteor meteor );

}

Тогда соответствующее событие может быть:

public final class Collision implements GameEvent<CollisionListener> {

   private final Spaceship ship;
   private final Meteor meteor;

   public Collision( final Spaceship aShip, final Meteor aMeteor ) {
      this.ship = aShip;
      this.meteor = aMeteor;
   }

   public void notify( final CollisionListener listener) {
      listener.spaceshipCollidedWithMeteor( ship, meteor );
   }

}

Вы можете представить диспетчера, который может распространить это событие на целевых слушателей, как в следующем сценарии (Events - это класс диспетчера):

// A unique dispatcher
final static Events events = new Events();

// Somewhere, an observer is interested by collision events 
CollisionListener observer = ...
events.listen( Collision.class, observer );

// there is some moving parts        
Spaceship aShip = ...
Meteor aMeteor = ...

// Later they collide => a collision event is notified trough the dispatcher
events.notify( new Collision( aShip, aMeteor  ) );

В этом случае диспетчеру не потребовалось никаких знаний о событиях и слушателях. Это вызывает отдельное уведомление о событии для каждого слушателя, используя только интерфейс GameEvent. Каждая пара событие / слушатель выбирает свои собственные условия диалога (они могут обмениваться многими сообщениями, если хотят).

Типичная реализация такого диспетчера должна выглядеть примерно так:

public final class Events {

   /** mapping of class events to active listeners **/
   private final HashMap<Class,ArrayList> map = new HashMap<Class,ArrayList >( 10 );

   /** Add a listener to an event class **/
   public <L> void listen( Class<? extends GameEvent<L>> evtClass, L listener) {
      final ArrayList<L> listeners = listenersOf( evtClass );
      synchronized( listeners ) {
         if ( !listeners.contains( listener ) ) {
            listeners.add( listener );
         }
      }
   }

    /** Stop sending an event class to a given listener **/
    public <L> void mute( Class<? extends GameEvent<L>> evtClass, L listener) {
      final ArrayList<L> listeners = listenersOf( evtClass );
      synchronized( listeners ) {
         listeners.remove( listener );
      }
   }

   /** Gets listeners for a given event class **/
   private <L> ArrayList<L> listenersOf(Class<? extends GameEvent<L>> evtClass) {
      synchronized ( map ) {
         @SuppressWarnings("unchecked")
         final ArrayList<L> existing = map.get( evtClass );
         if (existing != null) {
            return existing;
         }

         final ArrayList<L> emptyList = new ArrayList<L>(5);
         map.put(evtClass, emptyList);
         return emptyList;
      }
   }


   /** Notify a new event to registered listeners of this event class **/
   public <L> void notify( final GameEvent<L> evt) {
      @SuppressWarnings("unchecked")
      Class<GameEvent<L>> evtClass = (Class<GameEvent<L>>) evt.getClass();

      for ( L listener : listenersOf(  evtClass ) ) {
         evt.notify(listener);
      }
   }

}   

Я полагаю, он удовлетворяет вашим требованиям:

  • очень легкий,
  • быстро
  • без кастов (при использовании),
  • Все проверяется при компиляции время (без возможных ошибок),
  • Нет ограничений API для слушателей (каждое событие выбирай свои сообщения)
  • Evolutive (нет зависимости между разные события и / или слушатели),
  • Диспетчер - черный коробка
  • Потребителям и производителям не нужно знать друг друга.
0 голосов
/ 02 июня 2009

Java-бины должны были иметь этот интерфейс: он упрощает жизнь.

interface PropertyChangeProvider {
  void addPropertyChangeListener(PropertyChangeListener l);
  void addPropertyChangeListener(String property, PropertyChangeListener l);
  void removePropertyChangeListener(PropertyChangeListener l);
  void removePropertyChangeListener(String property, PropertyChangeListener l);
}

Реализуйте это повсюду.

Сделать класс классной доски (вероятно, одиночный. Это только эскиз)

public class Blackboard implements PropertyChangeListener,PropertyChangeProvider {

static Blackboard getInstance(){
    // implement this
}

void initialise(){
   // start the thread here
}

void republish(){
     // this can save you heartache too.
}


}

Дайте Blackboard поток, прослушайте события и переиздайте, используя свой собственный поток.

Классы могут просто публиковать свои события на доске.

Подписаться на доску для событий.

Если вы хотите сохранить события, разрешить повторную публикацию и т. Д.

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

0 голосов
/ 02 июня 2009

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...