2011-10-20 18 views
5

Próbuję stworzyć system reagowania na zdarzenia, które występują w mojej aplikacji, podobnie do wzorca obserwatora. W moim systemie, EventProducer s wydarzenia spust i EventConsumer s odpowiedzi na te wydarzenia, a dwa są połączone za pomocą centralnej piasty:Potrzebujesz pomocy przy użyciu Java Generics

Na razie mam zamiar ignorować EventProducer i skupić się na EventHub i EventConsumer:

interface EventConsumer<E extends Event> { 
    void respondToEvent(E event); 
} 

class EventHub { 
    private HashMap<Class</*event type*/>, HashSet<EventConsumer</*event type*/>>> subscriptions; 
    public <E extends Event> void fireEvent(E event) { 
     /* For every consumer in the set corresponding to the event type { 
      consumer.respondToEvent(event); 
     } */ 
    } 
    public <E extends Event> void subscribeToEvent(EventConsumer<E> consumer) { 
     /* Insert consumer into the set corresponding to E */ 
    } 
} 

problem leży w deklaracji HashMap: Chcę aby móc zrobić coś takiego

HashMap<Class<E extends Event>, HashSet<EventConsumer<E>>> 
// or 
<E extends Event> HashMap<Class<E>, HashSet<EventConsumer<E>>> 

Tak, że EventConsumer jest parametryzowane przez tego samego typu, Class jest, ale najbliżej można uzyskać jest

HashMap<Class<? extends Event>, HashSet<EventConsumer<? extends Event>>> 

Ale wtedy to pozwoliłoby rzeczy takie jak HashSet<EventConsumer<MouseClickEvent>> jest przypisany do Class<KeyPressEvent>, zakładając zarówno KeyPressEvent i MouseClickEvent podklasę Event.

Drugim problemem jest subscribeToEvent: Muszę być w stanie przechowywać konsumentów w prawidłowym zestawem odpowiadającym jego przypadku, podobnie jak w

subscriptions.get(E.class).put(consumer) 

ale nie mogę dostać klasę E w czasie wykonywania .

Jak mogę rozwiązać te problemy? Czy podchodzę do tego w niewłaściwy sposób?

Odpowiedz

1

Można usunąć generyczne z klasy EventConsumer. Ale musisz rzucić obiekt Event w każdej implementacji EventConsumer.

interface EventConsumer { 
    void respondToEvent(Event event); 
} 

class ClickEventConsumer implements EventConsumer { 
    public void respondToEvent(Event event){ 
    ClickEvent ce = (ClickEvent)event; 
    //... 
    } 
} 

class EventHub { 
    private HashMap<Class<? extends Event>, HashSet<EventConsumer>> subscriptions; 

    public void fireEvent(Event event) { 
    HashSet<EventConsumer> consumers = subscriptions.get(event.getClass()); 
    if (consumers != null){ 
     for (EventConsumer ec : consumers){ 
     ec.respondToEvent(event); 
     } 
    } 
    } 

    public void subscribeToEvent(Class<? extends Event> clazz, EventConsumer consumer) { 
    HashSet<EventConsumer> consumers = subscriptions.get(clazz); 
    if (consumers == null){ 
     consumers = new HashSet<EventConsumer>(); 
     subscriptions.put(clazz, consumers); 
    } 
    consumers.add(consumer); 
    } 
} 
+0

Właściwie to lubię to podejście. Pierwotnie chciałem móc zrobić coś w stylu 'class SomeConsumer implementuje EventConsumer , EventConsumer ', ale najwyraźniej nie możesz tego zrobić (co, jak sądzę, ma sens).Czy istnieje jakiś sposób, aby uzyskać pewien rodzaj polimorfizmu "zdarzenia" na konsumenta, aby uniknąć zmiany w 'responseToEvent'? Podobnie jak "void responseToEvent (ClickEvent); void replyToEvent (KeyPressEvent); '? –

+0

@AustinHyde Nie wiem. Bawiłem się z tym, ale nie mogłem znaleźć dobrego sposobu, aby to zrobić bez tworzenia całego zamieszania generycznego w 'EventHub'. Powinieneś także wziąć pod uwagę czytelność kodu. Nawet jeśli działa to trochę nieefektywnie, jeśli z czasem łatwiej je utrzymać, myślę, że warto (chyba że piszesz grę komputerową lub wyszukiwarkę lub coś, co liczy milisekundy). Dla mnie wszystkie te generyczne kody utrudniają odczytanie i zrozumienie kodu. – Michael

1

chodzi o mapę, bym go opuścić w następujący sposób:

HashMap<Class<? extends Event>, Set<EventConsumer<? extends Event>>> subscriptions; 

a następnie użyć parametryzowane metody dostępowe jak:

<E extends Event> void addSubscription(Class<E> eventClass, EventConsumer<? super E> eventConsumer) 

<E extends Event> Set<EventConsumer<? super E>> getSubscriptions(Class<E> eventClass) 

Jak już wskazał, że nie może uzyskać klasę zdarzeń w środowisku wykonawczym, więc musisz je udostępnić użytkownikom twoich interfejsów API, na przykład z podanym wyżej sygnaturą metody addSubscription.

0

Dlaczego nie sparametryzować swojej klasy EventHub? Jakieś wyzwania?

interface EventConsumer<E extends Event> { 
void respondToEvent(E event); 
} 

class EventHub<E extends Event> { 
private HashMap<Class<E>, HashSet<EventConsumer<E>>> subscriptions; 

public void fireEvent(E event) { 
/* 
* For every consumer in the set corresponding to the event type { 
* consumer.respondToEvent(event); } 
*/ 
    } 

    public void subscribeToEvent(EventConsumer<E> consumer) { 
    /* Insert consumer into the set corresponding to E */ 
} 
} 

Następnie użyj E we wszystkich swoich sygnaturach funkcji.

EDIT 1: porządku, ponieważ rozumiem Twoje pytanie jaśniej, jedziemy:

klasie EventConsumer może mieć EventType (y), że obsługuje/uchwyty. EvenType to Enum. W twojej Mapie przechowujesz Konsumentów przeciwko typowi EventType.

+0

Nie jest to opcja, ponieważ 'EventHub' musi obsługiwać więcej niż jeden typ' Event'. Jeśli zrobię 'nowy EventHub ', 'EventHub' będzie działał tylko dla' KeyPressEvent's, np. 'MouseClickEvent' –

+0

Twoja klasa EventConsumer może przechowywać EventType (s), które obsługuje/obsługuje. EvenType to Enum. W twojej Mapie przechowujesz Konsumentów przeciwko typowi EventType. –

2

Co można zrobić, to owinąć mapę własną, sparametryzowaną klasą. biorąc pod uwagę parametr klasy - możesz go użyć na mapie.coś takiego:

public class EventsMap<E extends Event> { 
    HashMap<Class<E>, HashSet<E>> map; 
} 

Jak do subskrybowania - Użyję odpowiedź ty1824 za ..

+1

Czy jest jakikolwiek powód, dla którego chciałbym go zawijać zamiast podklasy to: 'class EventsMap extends HashMap , HashSet >>' –

+1

@Austin Tak, są: 1. Będziesz tylko te metody publiczne, które są używane przez twoją implementację, zapewniające lepszą enkapsulację 2. Umożliwiłoby to jednostkowe przetestowanie implementacji bez testowania jednostki oryginalnej wersji HashMap 3. Pozostaniesz przy składzie Faworyzowanie przez Dziedziczenie http://en.wikipedia.org/wiki/Composition_over_inheritance – Gandalf

+0

@Gandalf Well * to * ciekawa koncepcja, o której nie myślałem. Będę musiał przyjrzeć się temu więcej, ale myślę, że może nawet dotyczyć większej ilości tej aplikacji niż to, co jest w tym pytaniu ... Dziękuję za tonę! –

0

Cóż, zaczynając od końca:

pierwsze, rzeczywiście można uzyskać ogólny typ parametru typu w czasie wykonywania . Tyle, że można to zrobić tylko w szczególnym przypadku:

static class A<E extends EventObject> { 
} 

static class B extends A<MouseEvent> { 
} 

public static void main(String[] args) { 
    System.out.println(B.class.getGenericSuperclass()); 
} 

Uwaga B jest non-generic, ale jest dziedziczona od rodziców rodzajowego.

Jeżeli nadrzędna jest typu parametryzowane, celem Type zwrócony musi dokładnie odzwierciedlać Konkretny rodzaj parametry wykorzystywane w kodzie źródłowym.

Czy można umieścić go w dowolnym użytku jest inna kwestia. Nie próbowałem. Większość kodu, który widziałem, przekazuje instancję jako Class.

Po drugie, tutaj Map nie może (o ile wiem) być naprawione za pomocą generycznych. Oczywiście możesz zaimplementować mapę bezpieczeństwa, ale myślę, że jest jeszcze jedna interesująca rzecz do rozważenia: kiedy wystrzeliwujesz wydarzenie, prawdopodobnie chcesz wysłać je wszystkim, którzy subskrybowali tę konkretną klasę zdarzeń i wszystkim, którzy zasubskrybowali jej rodziców . Więc tylko event.getClass() byłoby tylko trochę mniej niż wystarczające.

Powiązane problemy