Если вы хотите, чтобы ваш EventListener
мог потреблять Event
s, тогда его тип должен быть инвариантным или ковариантным (не объявлен out
). Если он позволит вам передать ваш EventListener<MyEvent>
, как если бы это был EventListener<Event>
, тогда ваш MyBus
класс может вызвать listener.handle(event)
с некоторым Event
, который не MyEvent
, например AnotherEvent
. Тогда вы получите ClassCastException
, когда он попытается преобразовать этот AnotherEvent
в MyEvent
.
Чтобы иметь возможность хранить различные типы инвариантных обработчиков событий, вам нужно будет удалить ограничения дисперсии, используя звездочку проекцию, и примените их, когда получите их с карты. Так что превратите ключи карты в объекты класса, а не просто в строки. Поскольку при работе со звездообразными типами у вас не будет помощи компилятора, вам нужно быть осторожным, чтобы вы добавляли в MutableMap только элемент того же типа, что и связанный с ним ключ класса. Затем, когда вы извлекаете элементы, приводите только к инвариантному типу.
Другая часть вашей проблемы заключается в том, что вашему конструктору нужен общий c тип. Сейчас он работает исключительно с Event
, поэтому не может обрабатывать подтипы Event
. Kotlin (пока?) Не поддерживает общие типы c для конструкторов, поэтому вам придется делать это с помощью фабричной функции.
Вот пример всего вышеперечисленного.
class MyBus() : EventBus {
private val eventListeners: MutableMap<Class<*>, MutableList<EventListener<*>>> = mutableMapOf()
override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
val listeners = retrieveListeners(aClass)
listeners.add(eventListener)
}
private fun <E: Event> retrieveListeners(aClass: Class<E>): MutableList<EventListener<E>> {
@Suppress("UNCHECKED_CAST")
return eventListeners.getOrPut(aClass) { mutableListOf() } as MutableList<EventListener<E>>
}
}
// Factory function
fun <E : Event> myBusOf(listeners: List<Pair<Class<E>, EventListener<E>>>): MyBus {
return MyBus().apply {
listeners.forEach {
registerListener(it.first, it.second)
}
}
}
И вы можете изменить тип заводского параметра с <List>Pair
на vararg Pair
, чтобы его было проще использовать.
Вот урезанный пример, объясняющий ограничение отклонения.
Ваш интерфейс для потребителя событий:
interface EventListener<E : Event> {
fun handle(event: E)
}
Две реализации Event
:
class HelloEvent: Event {
fun sayHello() = println("Hello world")
}
class BoringEvent: Event {}
Класс, реализующий интерфейс:
class HelloEventListener: EventListener<HelloEvent> {
override fun handle(event: HelloEvent) {
event.sayHello()
}
}
Теперь у вас есть EventListener, который может обрабатывать только HelloEvent
s. Попробуйте относиться к нему как к EventListener<Event>
:
val eventListener: EventListener<Event> = HelloEventListener() // COMPILE ERROR!
Представьте, что компилятор не помешал вам сделать это, и вы делаете следующее:
val eventListener: EventListener<Event> = HelloEventListener()
eventListener.handle(BoringEvent()) // CLASS CAST EXCEPTION AT RUN TIME!
Если бы это было разрешено, ваш HelloEventListener попробуйте вызвать sayHello()
в BoringEvent, у которого нет этой функции, поэтому он взломает sh. Это то, от чего вас могут защитить дженерики.
Теперь предположим, что ваш HelloEventListener.handle()
не звонил event.sayHello()
. Что ж, тогда он мог бы безопасно обработать BoringEvent. Но компилятор не делает за вас такой анализ. Он просто знает, что вы объявили, что HelloEventListener не может обрабатывать ничего, кроме HelloEvent.