У вас уже есть название нужного вам механизма - generics !
Сначала создайте свой класс событий:
abstract class AbstractEvent {
// Insert fields/methods common for all events here
}
Ничего странного в этом нет,Затем создайте параметризованный класс / интерфейс прослушивателя и присвойте его параметру типа верхнюю границу для класса объекта вашего события:
interface Listener<T extends AbstractEvent> {
void process(T event);
}
Теперь вы можете продолжать создавать свои определенные классы событий:
class PonyEvent extends AbstractEvent {
// Pony-specific stuff goes here
}
И, ну, это должно быть почти все, что вам нужно.Продолжайте и реализуйте ваши классы слушателей:
class LoggingPonyListener implements Listener<PonyEvent> {
@Override
public void process(PonyEvent event){
System.out.println("Pony event occurred: " + event);
}
}
Теперь у вас может возникнуть желание написать общий класс диспетчеризации событий, подобный этому:
class EventDispatcher<T extends AbstractEvent> {
private final List<Listener<T>> listeners =
new CopyOnWriteArrayList<Listener<T>>();
public void addListener(Listener<T> listener) {
listeners.add(listener);
}
public void dispatchEvent(T event) {
for (Listener<T> listener : listeners)
listener.process(event);
}
}
Выглядит довольно мило, а?Вы можете делать что-то вроде этого:
EventDispatcher<PonyEvent> dispatcher = new EventDispatcher<PonyEvent>();
dispatcher.add(new LoggingPonyListener());
dispatcher.dispatchEvent(new PonyEvent());
Совершенно мило, мы можем просто использовать этот материал, а затем, когда мы его используем, просто продолжать его повторное использование .Хотя есть одна проблема.Предположим, у вас есть умный разработчик, которому нужен супер-простой слушатель, который на самом деле ничего не делает с объектом события, а просто печатает указанное сообщение всякий раз, когда происходит событие.
Не совсем учитывая ваш класс EventDispatcher
, это было написано так:
class DebugListener implements Listener<AbstractEvent> {
private final String msg;
public DebugListener(String msg) { this.msg = msg; }
@Override
public void process(AbstractEvent event){
System.out.println(msg);
}
}
Это должно быть многоразово, верно?Нет, это не сработает:
EventDispatcher<PonyEvent> dispatcher = new EventDispatcher<PonyEvent>();
dispatcher.add(new DebugListener("pony event"));
, потому что DebugListener
- это Listener<AbstractEvent>
, а не Listener<PonyEvent>
.Чтобы решить эту проблему, можно использовать нижнюю границу для типа параметра:
class EventDispatcher<T extends AbstractEvent> {
private final List<Listener<? super T>> listeners =
new CopyOnWriteArrayList<Listener<? super T>>();
public void addListener(Listener<? super T> listener) {
listeners.add(listener);
}
public void dispatchEvent(T event) {
for (Listener<? super T> listener : listeners)
listener.process(event);
}
}
Это дает вам поведение, к которому вы стремитесь: точно так же, как вы можете отправить PonyEvent
на process
метод Listener<AbstractEvent>
(поскольку PonyEvent
является AbstractEvent
), теперь вы можете использовать класс диспетчера событий, параметризованный с типом, для запуска слушателей, параметризованных одним из его супертипов.