generics - Kotlin 中带有泛型的优雅事件处理程序
问题描述
我正在尝试使用按类索引的事件构建事件处理程序。您将注册以侦听类事件InterestingEvent : Event
(例如),并且每当InterestingEvent()
向经理发送新消息时都会收到通知。
我无法让 Kotlin 通用代码正常工作。这是迄今为止我能做的最好的:
class EventManager{
private val listeners: MutableMap<KClass<out Event>, MutableList<EventListener<in Event>>> = HashMap()
fun <T : Event> register(event: KClass<out T>, listener: EventListener<T>) {
val eventListeners: MutableList<EventListener<T>> = listeners.getOrPut(event) { ArrayList() }
eventListeners.add(listener)
}
fun notify(event: Event) {
listeners[event::class]?.forEach { it.handle(event) }
}
}
我的getOrPut
电话想要一个MutableList<EventListener<T>>
但找到MutableList<EventListener<in Event>>
了。
理想情况下,我也会摆脱 KClass 参数,register
但我认为这是不可能的。
有可能做我在这里尝试的吗?
解决方案
这有点困难,因为您想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
}
这很有效,因为它是一个独立的系统,我们对类型安全性的完全无视并没有真正以危险的方式暴露出来,因此“足够安全”。如果我想到更好的方法,我会更新帖子。一种方法是创建一个新的 Map-of-class-to-lists,它有一个关联的契约,试图将值类型与键类型连接起来,并帮助消除强制转换。
推荐阅读
- java - 通过应用程序端点将 websocket 连接到本地应用程序
- swift - 如何调用 CompletionHandler 方法?
- ssis - 导出到 Excel 过程的 SSIS 自动化
- javascript - 如何编写可重用的错误?
- python - 如何遍历 Pandas 行并根据其在行中的排名修改每个单元格?
- testing - 在带有端口的 localhost 上使用 reCAPTCHA
- javascript - 更新 Chart.js 图表时,数据已更新,但画布已清空
- foreach - Drupal,为 foreach 提供的参数无效
- angularjs - 在对象内的数组中使用 Object.create 方法未绑定到 ng-model
- php - 在 laravel 5.4 中为经过身份验证的用户创建全局变量