Подтвердить что ты не робот

Простая система диспетчеризации сообщений Java

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

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

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

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();
        //...
     }
   }
}

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

Идеи?

4b9b3361

Ответ 1

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

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

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 );
   }

}

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

// 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( ship, meteor  ) );

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

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

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