简单的java消息调度系统

Bra*_*ugh 19 java events design-patterns

我正在开发一个可以发生各种事件的小型Java游戏.至少有几十个基本事件,各种事件处理程序可能会感兴趣.代码中还有几个地方可能会触发这些事件.我不想强迫事件监听器知道他们需要注册哪个类,而是想创建某种集中式的消息调度系统,有些类会将事件提交到,感兴趣的类可以挂钩来监听某些类型事件.

但我有一些问题.首先,这似乎是一个明显而常见的问题.是否有简单的虚拟机内消息系统的最佳实现?好像会有.

其次,更重要的是,我正在尝试为调度类找出一种相当优雅的方式来尽可能少地了解消息的类型.我希望能够在不修改消息调度程序的情况下创建新类型的事件.但是,我有一个相反的担忧.我真的很喜欢处理方法的方法签名要清楚.换句话说,我更喜欢以下内容:

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

更通用,更难阅读的东西:

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();
        //...
     }
   }
}
Run Code Online (Sandbox Code Playgroud)

但是我没有看到任何好的方法将类型特定的知识保留在调度程序之外,同时保持方法签名的清晰和可读性.

想法?

Lau*_*mon 40

如果每个事件都有特定的侦听器接口.每个事件都能够自己发出侦听器调用.然后,调度程序的角色是识别目标侦听器并触发它们的事件通知.

例如,通用事件定义可以是:

public interface GameEvent<L> {

   public void notify( final L listener);
}
Run Code Online (Sandbox Code Playgroud)

如果你的CollisionListener是:

public interface CollisionListener {

    public void spaceshipCollidedWithMeteor( Spaceship spaceship, Meteor meteor );

}
Run Code Online (Sandbox Code Playgroud)

然后,相应的事件可以是:

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

}
Run Code Online (Sandbox Code Playgroud)

您可以想象一个能够在目标侦听器上传播此事件的调度程序,如下面的场景(事件是调度程序类):

// 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  ) );
Run Code Online (Sandbox Code Playgroud)

在这种情况下,调度员不需要任何关于事件和监听器的知识.它仅使用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);
      }
   }

}   
Run Code Online (Sandbox Code Playgroud)

我想它符合你的要求:

  • 很轻,
  • 快速,
  • 没有演员阵容(在使用中),
  • 在编译时检查每件事(没有可能的错误),
  • 对侦听器没有API约束(每个事件选择它自己的消息),
  • 进化(不同事件和/或听众之间没有依赖关系),
  • 调度员是一个通用的黑匣子,
  • 消费者和生产者不需要彼此了解.

  • 一种解决方案是复制从listenersOf返回的侦听器,以便将通知发送到侦听器的副本,使本地Map同时静音. (3认同)