質問

I'm writing Event Source template for my classes:

public class EventSource<E> {
  protected ArrayList<E> listeners = new ArrayList<E>();

  public void addListener(E listener)
  {
    listeners.add(listener);
  }

  public void removeListener(E listener)
  {
    listeners.remove(listener);
  }
}

To fire an event I need to call something like:

for(SomeListener listener: listeners)
  listener.onSomeEvent();

This looks quite ugly to fire an event. Is there a way to call something shorter like:

fire(SomeListener.onSomeEvent)

or

fire(onSomeEvent)

?

I've already found 3 solutions:

I Use java reflections and pass method name as argument to some method in template, which will call this method for all the listeners. But I think it is inefficient because of needed parsing the string (method name). And how to pass parameters in not the ugly way - question remains.

II Use the same name for all the methods, but pass different parameter:

public struct SomeEvent1
{
  int someData;
}

public struct SomeEvent2
{
  int someAnotherData;
  double probablyMoreData;
}

public interface SomeListener
{
  public void fire(SomeEvent1 e);
  public void fire(SomeEvent2 e);
}

But it requires building this event structs even if I don't need any parameters.

III Using delegation. Imagine, that SomeEventSource extends EventSource. Now I can call onSomeEventI(), because I've written those methods in SomeEventSource:

public void onSomeEventI()
{
  for(SomeListener listener: listeners)
    listener.onSomeEventI();
}

But I can't write those methods in generic EventSource, because it is Listener-agnostic. And writing those methods in SomeEventSource makes generic EventSource more useless, than usefull.

The question remains: how to fire an event in the short way?

役に立ちましたか?

解決

I think Event API in general should cover following things:

  1. Be type safe, avoid casting of Events/Event Types classes - it will save you from unexpected class cast Exceptions in the future.
  2. Be developer friendly. API should be short and sweet. You should be able to easily find the right listener within your IDE (e.g. Intellij IDEA or Eclipse). Type safety will give you that.
  3. Adding of new event types should be easy and fast.
  4. Event structures should be reusable.

Here is one of possible approaches:

Create base event and more complex event structure.

class BaseEvent {
}

class ComplexEvent extends BaseEvent {
    int importantData;
    public ComplexEvent(int i) {
        importantData = i;
    }
}

Create a separate class for EventType, make each event type dependent on event. We now can reuse existing structures for our event types. It is also very easy to add new Events without breaking the existing code.

final class EventType<E> {
    private EventType(){}
    public final static EventType<BaseEvent> SimpleEvent = new EventType<BaseEvent>();
    public final static EventType<BaseEvent> SimpleEvent2 = new EventType<BaseEvent>();
    public final static EventType<ComplexEvent> ComplexEvent1 = new EventType<ComplexEvent>();
    public final static EventType<ComplexEvent> ComplexEvent2 = new EventType<ComplexEvent>();
}

Create listener as a generic interface, dependent on Event.

interface Listener<E extends BaseEvent> {
    void handle(E event);
}

Use this classes within EventSource, register listeners for specified event types.

class EventSource {

    private final Map<EventType, List<Listener<? extends BaseEvent>>> listenersMap = new HashMap<EventType, List<Listener<? extends BaseEvent>>>();

    public <E extends BaseEvent> void addListener(EventType<E> eventType, Listener<E> listener) {
        listeners(eventType).add(listener);
    }

    public <E extends BaseEvent> void fire(EventType<E> eventType, E event) {
        for (Listener listener : listeners(eventType)) {
            listener.handle(event);
        }
    }

    private List<Listener<? extends BaseEvent>> listeners(EventType eventType) {
        if (listenersMap.containsKey(eventType)) {
            return listenersMap.get(eventType);
        } else {
            List<Listener<? extends BaseEvent>> listenersList = new ArrayList();
            listenersMap.put(eventType, listenersList);
            return listenersList;
        }
    }

}

Check usage of API, it allows us to get the right event structure in Listener's implementations.

public static void main(String[] args) {
        EventSource eventSource = new EventSource();
        eventSource.addListener(EventType.SimpleEvent, new Listener<BaseEvent>() {
            @Override
            public void handle(BaseEvent event) {
                log.info("Simple 1 handled!");
            }
        });
        eventSource.addListener(EventType.SimpleEvent2, new Listener<BaseEvent>() {
            @Override
            public void handle(BaseEvent event) {
                log.info("Simple 2 handled!");
            }
        });
        // compile error! we must handle ComplexEvent type
//        eventSource.addListener(EventType.ComplexEvent1, new Listener<BaseEvent>() {
//            @Override
//            public void handle(BaseEvent event) {
//                log.info("Complex 1 handled!");
//            }
//        });
        eventSource.addListener(EventType.ComplexEvent1, new Listener<ComplexEvent>() {
            @Override
            public void handle(ComplexEvent event) {
                log.info("Complex 1 handled!" + event.importantData);
            }
        });
        eventSource.addListener(EventType.ComplexEvent2, new Listener<ComplexEvent>() {
            @Override
            public void handle(ComplexEvent event) {
                log.info("Complex 2 handled!" + event.importantData);
            }
        });
        eventSource.fire(EventType.SimpleEvent, new BaseEvent());
        eventSource.fire(EventType.SimpleEvent2, new BaseEvent());
        eventSource.fire(EventType.ComplexEvent1, new ComplexEvent(1));
        eventSource.fire(EventType.ComplexEvent2, new ComplexEvent(2));
        // compile error! we must fire ComplexEvent to our listeners
        //eventSource.fire(EventType.ComplexEvent1, new BaseEvent());
    }

You can also add a shorthand fire call to EventSource when you don't need to pass any specific data

public <E extends BaseEvent> void fire(EventType<E> eventType) {
    fire(eventType, (E) new BaseEvent());
}

And use it from the client code as simple:

eventSource.fire(EventType.SimpleEvent);
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top