简单的java消息分派系统

2022-09-02 12:07:30

我正在开发一个小型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();
        //...
     }
   }
}

但是我没有看到任何好的方法可以将特定于类型的知识排除在调度程序之外,同时保持方法签名的干净和可读性。

想法?


答案 1

如果每个事件都有特定的侦听器接口。每个事件都能够发出侦听器调用本身。然后,调度程序的角色是标识目标侦听器并触发其上的事件通知。

例如,泛型事件定义可以是:

public interface GameEvent<L> {

   public void notify( final L listener);
}

如果您的碰撞观察器是:

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约束(每个事件选择自己的消息),
  • 演化(不同事件和/或侦听器之间没有依赖关系),
  • 调度程序是一个通用的黑匣子,
  • 消费者和生产者不需要相互了解。

答案 2

如果你想避免 ,那么你唯一的赌注就是使用继承将方法调用路由到正确的方法。不能使用方法重载,因为这是在编译时由要传递给方法的变量的声明类型决定的。您必须使用继承。instanceof

如果您不能使用继承,那么您唯一的其他选择(据我所知)涉及很多.instanceof

关于消息传递系统,您可以使用 ActiveMQ 作为 JVM 中的消息传输。您不必通过套接字或其他方式使用它。我无法想象ActiveMQ对于您的手段来说不够有效。