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常にタイプセーフではありません。(またはに変更Listenerすると機能しません。私は本当に試しました。)Listener<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);
                }
            }
        }
    }
}

リスナー インターフェイスが若干変更されました

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()でき、結果をマップにキャッシュすることもできるので、どのリスナーカテゴリを起動する必要があるかがわかりますが、他の誰かが言ったように、列挙型または int を使用する方が簡単ではるかに効率的です-フィールドを型システムとして使用します。

于 2013-05-16T01:20:43.920 に答える