我正在开发一个可以发生各种事件的小型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();
//...
}
}
}
但是我没有看到任何好的方法将类型特定的知识保留在调度程序之外,同时保持方法签名的清晰和可读性。
想法?
答案 0 :(得分:40)
如果每个事件都有特定的侦听器接口。每个事件都能够自己发出侦听器调用。然后,调度程序的作用是识别目标侦听器并触发它们的事件通知。
例如,通用事件定义可以是:
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);
}
}
}
我认为它符合您的要求:
答案 1 :(得分:0)
如果您想避免使用instanceof
,那么您唯一的选择就是使用继承将方法调用路由到正确的方法。您不能使用方法重载,因为这是在编译时由传递给方法的变量的声明的类型决定的。你必须使用继承。
如果你不能使用继承,那么你唯一的另一个选择(我知道)涉及很多instanceof
。
关于消息传递系统,您可以使用ActiveMQ作为JVM内部消息传输。您不必通过套接字或其他方式使用它。我无法想象ActiveMQ的效率不够高。
答案 2 :(得分:0)
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.
}
}
给Blackboard一个线程,监听事件并使用自己的线程重新发布。
课程可以将他们的活动发布到黑板上。
订阅黑板上的活动。
如果你想要,你可以坚持事件,允许重新发布等。
对于应用程序中的内容,它非常好。 (和数据交换接口一样工作!)