实现事件驱动架构的更安全的类型方式?

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中的一个始终不安全。(在EventDispatcher中将Listener更改为Listener<Event>Listener<? extends Event>并不起作用。我真的尝试过。)
是否有可能实现具有相同灵活性(可扩展的事件和侦听器,一个单独的分派程序处理不同的侦听器),但没有不安全的代码的架构呢?
2个回答

1

首先,建议您不要使用类进行比较,而是使用EventType枚举类型代替,但替换相对简单。

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

至于问题,您没有在Listener接口上使用getEventType()方法。另外,在onEvent()方法中不需要传入T类型的对象。

大部分更改都是针对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接口的类中会丢失信息,这可能是一个致命缺陷。


这个解决方案大部分是正确的,但是从onEvent中删除参数化方法参数会使整个东西的泛型变得毫无意义。 - jonathan.cone
@jonathan.cone - 嗯,进退两难。由于类型擦除的缘故,我不确定是否有一种类型安全的方法来做这件事。 - Craig
经过几天的研究,我现在相信在这种情况下最好的方法不是使用通用类型。因此,我选择这个作为最佳答案。 - Lai Yu-Hsuan

0
如果您在dispatch()内部声明了一些泛型类型参数,您可以让Java将事件子类型绑定到这些方法并选择适当的类型。
由于现在没有这样做,
Class<? extend Event> eventType = event.getEventType();

不将事件子类型绑定到任何命名类型参数;因此信息无法传递并且不能被编译器进一步使用。

例如:

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()来检查继承关系,甚至可以将结果缓存到映射中,以便知道应该触发哪些侦听器类别,但正如其他人所说,使用枚举或int字段作为您的类型系统更简单、更有效率。


Class<ET> eventType = event.getEventType(); 这行代码甚至无法编译。 - Lai Yu-Hsuan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接