3

我正在尝试在 Java 中实现可扩展的偶数驱动架构。不幸的是,我不能让它完全安全。以下是我所做的。

首先我定义事件。这是一个几乎是空的类来扩展。

public abstract class Event {
    public final Class<? extends Event> getEventType() {
        return this.getClass();
    }
}

和听众:

public interface Listener<T extends Event> {
    Class<T> getEventType();
    void onEvent(T event);
}

到现在为止还挺好。但是当我尝试实现事件调度程序时,我被卡住了。

public class EventDispatcher {
    private Map<Class<? extends Event>, Collection<Listener>> listenersDict = new HashMap<>();

    public void registerListener(Listener listener) {
        Class<? extends Event> eventType = listener.getEventType();
        Collection<Listener> listeners = listenersDict.get(eventType);

        if(listeners == null) {
            listeners = new ArrayList<>();
            listenersDict.put(eventType, listeners);
        }
        listeners.add(listener);
    }

    public void dispatch(Event event) {
        Class<? extend Event> eventType = event.getEventType();
        Collection<Listener> listeners = listenersDict.get(eventType);

        if(listeners != null) {
            for(Listener listener : listeners) {
                @SuppressWarnings("unchecked") // Necessary Evil?
                listener.onEvent(event);
            }
        }
    }
}

如您所见,我必须使用@SuppressWarnings. 我尝试了所有我能想到的方法,但其中一种方法registerListener总是dispatch类型不安全的。(更改ListenerListener<Event>或更改不起作用。我真的尝试过。Listener<? extends Event>EventDispatcher

是否可以实现具有相同灵活性的架构(可扩展的事件和侦听器,单个调度程序处理不同的侦听器),但没有不安全的代码?

4

2 回答 2

1

首先,我建议您不要使用类进行比较,而是使用EventType枚举并使用它,但这很容易替换。

其次,请原谅我使用 java 1.6(注意<>语法的替换)。

至于问题,您没有使用界面getEventType()上的方法。Listener此外,您不需要TonEvent()方法中接收类型的对象。

大部分更改是对 EventDispatcher 进行的:

public class EventDispatcher {
    private Map<Class<? extends Event>, Collection<Listener<? extends Event>>> listenersDict = new HashMap<Class<? extends Event>, Collection<Listener<? extends Event>>>();

    public void registerListener(Listener<? extends Event> listener) {
        Class<? extends Event> eventType = listener.getEventType();
        Collection<Listener<? extends Event>> listeners = listenersDict.get(eventType);

        if(listeners == null) {
            listeners = new ArrayList<Listener<? extends Event>>();
            listenersDict.put(eventType, listeners);
        }
        listeners.add(listener);
    }

    public void dispatch(Event event) {
        Class<? extends Event> eventType = event.getEventType();
        Collection<Listener<? extends Event>> listeners = listenersDict.get(eventType);

        if(listeners != null) {
            for(Listener<? extends Event> listener : listeners) {
                if (listener.getEventType() == eventType) {
                    listener.onEvent(event);
                }
            }
        }
    }
}

Listener 界面略有变化

public interface Listener<T extends Event> {
    Class<T> getEventType();

    void onEvent(Event event);
}

Event 类没有变化:

public abstract class Event {
    public final Class<? extends Event> getEventType() {
        return this.getClass();
    }
}

编辑:

作为对@jonathan.cone 的回应,我想指出这个解决方案确实会在实现Listener 接口的类中丢失信息,这可能会破坏交易。

于 2013-05-16T01:20:15.423 回答
0

如果您在 dispatch()的方法上声明了一些通用类型参数,您可以让 Java 将 Event 子类型绑定到这些方法并选择适当的类型。

照原样,

Class<? extend Event> eventType = event.getEventType();

不将 Event 子类型绑定到任何命名的类型参数;所以信息无处可去,编译器无法进一步使用。

例如:

public <ET extends Event>  void dispatch (ET event) {
    Collection<Listener<ET>> listeners = getListeners( event);
    if(listeners != null) {
        for (Listener<ET> listener : listeners) {
            listener.onEvent( event);
        }
    }
}

private <ET extends Event>  Collection<Listener<ET>> getListeners (ET event) {
    Class<ET> eventType = event.getEventType();   // might work, if you genericized Event on itself..
    Collection<Listener<ET>> listeners = (Collection<Listener<ET>>)(Collection) listenersDict.get( eventType);  // will always needs cast.
}

但是,您可能会发现——在某个时刻——编译器无法完全“类型验证”。别担心。


您还会发现“完全匹配”一个类并不理想——因为理论上任何注册接收超类型的侦听器都应该接收子类型事件。

可以使用 来检查与继承的匹配Class.isAssignableFrom(),您甚至可以将结果缓存到地图中,以便知道应该触发哪些侦听器类别,但是 - 正如其他人所说 - 使用 Enum 或 int- 更简单且更有效 -字段作为您的类型系统。

于 2013-05-16T01:20:43.920 回答