Это немного сложно, потому что вы хотите сохранить набор из EventListener<T: Event>
элементов в списках на одной карте, так что T
неизвестно для любого данного списка.Затем вы хотите использовать элементы из списка, как если бы T
было известно, поэтому его можно передать notify()
.Так что это вызывает проблемы.Многие библиотеки просто уведомляют об общем событии, и получатель выполняет кастинг.Но вы можете сделать это несколькими способами, чтобы нести это бремя в свою библиотеку уведомлений, поскольку она знает, что логически существует гарантия связи между ключом карты и типами элементов списка.
Вот несколько скорректированных кодов с двумя формами уведомлений.
class EventManager {
val listeners: MutableMap<KClass<*>, MutableList<EventListener<out Event>>> = mutableMapOf()
inline fun <reified T : Event> register(listener: EventListener<T>) {
val eventClass = T::class
val eventListeners: MutableList<EventListener<out Event>> = listeners.getOrPut(eventClass) { mutableListOf() }
eventListeners.add(listener)
}
inline fun <reified T: Event> notify(event: T) {
// here we have an unsafe action of going from unknown EventListener<T: Event> to EventListener<R>
// which we know it is because of our code logic, but the compiler cannot know this for sure
listeners[event::class]?.asSequence()
?.filterIsInstance<EventListener<T>>() // or cast each item in a map() call
?.forEach { it.handle(event) }
}
// or if you don't know the event type, this is also very unsafe
fun notifyUnknown(event: Event) {
listeners[event::class]?.asSequence()
?.filterIsInstance<EventListener<Event>>()
?.forEach { it.handle(event) }
}
}
Для некоторых простых примеров классов:
open class Event(val id: String)
class DogEvent : Event("dog")
class CatEvent: Event("cat")
interface EventListener<T: Event> {
fun handle(event: T): Unit
}
Работает следующий тест:
fun main() {
val mgr = EventManager()
mgr.register(object : EventListener<DogEvent> {
override fun handle(event: DogEvent) {
println("dog ${event.id}")
}
})
mgr.register(object : EventListener<CatEvent> {
override fun handle(event: CatEvent) {
println("cat ${event.id}")
}
})
mgr.notify(Event("nothing")) // prints: <nothing prints>
mgr.notify(CatEvent()) // prints: cat cat
mgr.notify(DogEvent()) // prints: dog dog
mgr.notifyUnknown(CatEvent()) // prints: cat cat
mgr.notifyUnknown(DogEvent()) // prints: dog dog
}
Это хорошо работает, поскольку это автономная система, где полное игнорирование, которое мы показываем безопасности типов, на самом деле не подвергается опасным воздействиям и, следовательно, является "достаточно безопасным".Если я подумаю о лучшем способе, я обновлю публикацию.Одним из методов может быть создание новой Карты-класса-списков, с которой был связан контракт, который пытается связать тип значения с типом ключа и помогает устранить приведение.