问题描述
我正在制作一个小Java游戏,其中可能发生各种事件.至少有几十个基本事件可能会感兴趣.代码中还有几个地方可能会触发这些事件.我想创建某种类型的集中式消息签名系统,而不是强迫活动听众知道他们需要注册的类事件.
但是我有一些问题.首先,这似乎是一个显而易见的问题.是否有简单的IN-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(); //... } } }
,但我看不到任何很好的方法可以将特定于类型的知识放在调度员之外,同时保持方法签名清洁和可读.
想法?
推荐答案
如果每个事件都有一个特定的侦听器接口.每个事件都可以发出听众呼唤自己.然后,调度员的作用是识别目标听众并触发事件通知.
例如,通用事件定义可以:
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( 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约束(每个事件 选择自己的消息),
- 进化(没有依赖关系 不同的事件和/或听众),
- 调度员是通用黑色 盒子,
- 消费者和生产者不需要 彼此认识.
其他推荐答案
如果要避免instanceof,那么您唯一的赌注是使用继承将方法调用路由正确的方法.您不能使用方法过载,因为这是由您传递给方法的变量的声明的类型在编译时间确定的.您必须使用继承.
如果您不能使用继承,那么您唯一的其他选择(我知道)涉及很多instanceof.
关于消息传递系统,您可以将 activemq 作为In-the-jvm消息传输.您不必通过插座或其他方式使用它.我无法想象ActiveMQ对您的手段不够有效.
其他推荐答案
Java Bean应该具有此接口:它使生活更简单.
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. } }
给黑板一个线程,收听事件并使用自己的线程重新发布.
课程只能将其事件发布到黑板上.
订阅黑板进行活动.
如果您愿意,可以坚持事件,允许重新发布等.
对于应用程序中的某些内容,这非常好. (也可以工作以及数据互换接口!)
问题描述
I'm working on a little Java game in which all sorts of events can happen. There are at least a couple of dozen basic events that various event handlers may be interested in. There are also several places in the code where these events might be triggered. Rather than forcing the event listeners to know which class they need to register with, I'd like to create some sort of a centralized message-dispatching system, which some classes would submit events into and interested classes could hook into to listen for certain kinds of events.
But I have some questions. Firstly, this seems like an obvious and common problem. Are there favorite implementations of simple, in-VM messaging systems? Seems like there would be.
Secondly, and more importantly, I'm trying to work out a reasonably elegant way for the dispatching class to know as little as possible about types of messages. I'd love to be able to create new kinds of events without modifying the message dispatcher at all. However, I have an opposite concern. I'd really like for the method signatures of the handling methods to be clear. In other words, I would prefer the following:
public class CollisionConsoleHandler implements CollisionListener { @Override public void spaceshipCollidedWithMeteor( Spaceship spaceship, Meteor meteor ) { //... } }
over something more generic and harder to read:
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(); //... } } }
But I don't see any good ways to keep type-specific knowledge out of the dispatcher while at the same time keeping the method signatures clean and readable.
Ideas?
推荐答案
If there is a specific listener interface for each event. Each event is able to issue listeners calls itself. Then, the role of the dispatcher is to identify target listeners and to trigger the event notification on them.
For example, a generic event definition can be:
public interface GameEvent<L> { public void notify( final L listener); }
If your CollisionListener is:
public interface CollisionListener { public void spaceshipCollidedWithMeteor( Spaceship spaceship, Meteor meteor ); }
Then, the corresponding event can be:
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 ); } }
You can imagine a dispatcher which is able to propagate this event on the target listeners like in the following scenario (Events is the dispatcher class):
// 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 ) );
In this scenario, the dispatcher did not require any knowledge on events and listeners. It triggers an individual event notification to each listener, using only the GameEvent interface. Each event/listener pair chooses it's own dialog modalities (they can exchange many messages if they want).
A typical implementation of a such dispatcher should be something like:
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); } } }
I suppose its fulfills your requirements:
- very light,
- fast,
- no casts (at usage),
- Every thing is checked at compile time (no possible mistake),
- No API constraints on listeners (each event choose it's own messages),
- Evolutive (no dependencies between different events and/or listeners),
- The dispatcher is a generic black box,
- Consumers and producers don't need to know each other.
其他推荐答案
If you want to avoid instanceof, then your only bet is to use inheritance to route a method call to the right method. You cannot use method overloading, since that is decided at compile time by the declared type of the variable you are passing to a method. You have to use inheritance.
If you cannot use inheritance, then your only other choice (that I'm aware of) involves a lot of instanceof.
Regarding a messaging system, you can use ActiveMQ as an in-the-JVM message transport. You do not have to use it via socket or other means. I can't imagine that ActiveMQ would not be efficient enough for your means.
其他推荐答案
Java beans should have had this interface: it makes life simpler.
interface PropertyChangeProvider { void addPropertyChangeListener(PropertyChangeListener l); void addPropertyChangeListener(String property, PropertyChangeListener l); void removePropertyChangeListener(PropertyChangeListener l); void removePropertyChangeListener(String property, PropertyChangeListener l); }
Implement it all over the place.
Make a blackboard class (probably a singleton. this is a sketch only)
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. } }
Give Blackboard a thread, listen for events and republish using its own thread.
Classes can just publish their events to the blackboard.
Subscribe to the blackboard for events.
If you want you can persist events, allow republishing etc.
For something inside an app it is very good. (works just as well as a data interchange interface as well!)