views:

64

answers:

3

Hello I have written custom dispathing/handling event system that generally look like this:

Event handler interface:

public interface EventHandler{
}

Base event class:

public abstract class Event<H extends EventHandler> {
    public static Class Type<H> { }
    public abstract void dispatch(H handler);
}

Handler manager:

public class HandlerManager {
    private Map<Event.Type, List<EventHandler>> map = new HashMap<Event.Type, List<EventHandler>>();
    public void register(Event.Type<H> type, H handler) {
        if(map.get(type) == null) { map.put(type, new ArrayList<EventHandler>()); }
        map.get(type).add(handler);
    }

    public void fire(Event<H> event) {...}
    ...
}

And everything is working fine but i want to use events like

public class DataChangeEvent<D> extends Event<DataChangeHandler<D>> {
    public static final Type<?> TYPE = new Type<?>();
    D data;
    ...
    public void dispatch(DataChangeHandler<D> handler) {
        handler.onDataChanged(this);
    }
    public D getData() { return data; }
}

public class DataChangeHandler<D> extends EventHandler {
    void onDataChanged(DataChangeEvent<D> event);
}

and now when I register handler DataChangeHandler with manager that generates events for Strings and for example for Integers, this registered handler will receive both events what causes ClassCastException to occure when I want to read data. I understand that generic dont have some special class and that despite of type defined in DataChangeHandler they are stored in the same list in handlers map.

Is there any way to make it work?

A: 

Have a look here.

walters
+1  A: 

This seems like a really, really smelly design. Why should an event be typed with the class that handles that type of event? That's backwards. An EventHandler should be typed with the type of Events it handles.

So I didn't quite follow what you're actually trying to do but I think you're basically trying to do this:

private Map<Class<?>, List<EventHandler>> map;
public <T> void register(Class<? extends T> typeFilter, EventHandler<T> handler) {
    map.get(typeFilter).add(handler);
}


//...later
//safe since we used a generic method to add 
@SuppressWarnings("unchecked"); 
public void fire(Event<?> event) {
    for ( EventHandler handler : map.get(event.getClass()) ) {
        handler.onDataChanged(event);
    }
}

//or similarly:
@SuppressWarnings("unchecked"); 
public void fire(Event<?> event) {
    for ( Class<?> type : map.keySet() ) {
        if ( !type.instanceOf(event) ) continue;
        for ( EventHandler handler : map.get(type) ) {
            handler.onDataChanged(event);
        }
    }
}

This type of design will filter out events that the handler can't handle.

Mark Peters
A: 

Generics are largely a compile time feature. If you need the type at runtime you need to pass the class as an arguement and store it in a field.

IMHO: A more elegent way of creating a dispacter is to use an annotation. Like

@EventHandler
public void onMyEvent(MyEvent event) {
   // is called when MyEvent is dispacted.
}
Peter Lawrey