gemini
这是一个非常工整且符合 Kotlin 风格的事件总线实现,使用了 sealed class 做类型限制,使用了并发集合保证线程安全,整体架构清晰。
但是,作为一个通用框架的核心组件,目前的实现存在 1 个致命逻辑缺陷(多态失效),2 个设计隐患,以及 1 个性能优化点。
以下是详细的 Code Review:
1. 🛑 致命 BUG:事件多态性失效
这是当前实现中最严重的问题。
现象:
如果你监听父类事件,通过子类触发,监听器不会被执行。
Kotlin
// 注册监听父类 AppEvent
eventBus.on<AppEvent> { event ->
println("Received an event: $event")
}
// 发射子类 ServerStarting
eventBus.emit(ServerStarting(colleen))结果: 监听器不会收到通知。
原因:
在 emit 方法中:
Kotlin
val eventListeners = listeners[event::class.java] ?: return这里使用的是 Strict Exact Match(严格精确匹配)。ServerStarting::class.java 和 AppEvent::class.java 是两个不同的 Key。当你 emit 一个 ServerStarting 实例时,Map 中查找的是 ServerStarting 的 Key,而你的监听器注册在 AppEvent 的 Key 下。
修复建议:
在 emit 时,需要向上遍历事件的类继承层级(或接口),触发生命周期内所有父类型的监听器。
2. ⚠️ 设计隐患:transform 覆盖问题
在 RequestReceived 和 ResponseGenerated 中,你设计了修改流的机制:
Kotlin
var transform: ((Request) -> Request)? = null问题:
如果有两个中间件(监听器)同时监听 RequestReceived:
中间件 A 设置了
transform = { r -> r.header("A", "1") }中间件 B 设置了
transform = { r -> r.header("B", "2") }
结果: B 会直接覆盖 A 的赋值。A 的逻辑丢失了。
修复建议:
不要使用单个函数变量,而是使用一个调用链或者List,或者让 Request 本身即使是不可变的,也能通过 Event 传递累积的变化。更简单的做法是让 transform 支持组合:
Kotlin
fun addTransform(newTransform: (Request) -> Request) {
val old = transform
transform = if (old == null) newTransform else { r -> newTransform(old(r)) }
}3. ⚠️ 逻辑缺失:currentTarget 未更新
你定义了 currentTarget,这在 DOM 事件模型中非常重要(区分事件原始目标和当前冒泡到的层级),但在 emit 的冒泡逻辑中,你没有更新它。
Kotlin
if (event.bubbles && !event.isPropagationStopped) {
parent?.emit(event) // 直接透传,currentTarget 还是 null 或者原来的值
}修复建议:
EventBus 需要知道它“依附”于哪个对象(Owner)。当事件流经这个 Bus 时,将 currentTarget 设置为该 Owner。如果 EventBus 是独立的,这个字段可能没有意义。
4. ⚡ 性能优化:off 方法效率低
Kotlin
fun off(listener: EventListener<*>) {
listeners.values.forEach { it.remove(listener) }
}问题: 这是一个 $O(N \times M)$ 的操作(N=事件类型数,M=监听器数)。每次注销都要遍历整个 Map 的所有 List。
修复建议:
让 EventListener 记住它注册的 Class 类型,或者返回一个实现了 Disposable 接口的对象,该对象内部持有 clazz 和 listener 引用,直接定位删除。
🛠️ 优化后的代码实现 (Refactored Code)
针对以上问题,这是优化后的版本。主要改动:
支持多态分发(缓存了类层级查找)。
优化取消订阅(返回
Subscription)。Transformer 组合(防止覆盖)。
Kotlin
package site.daydream.colleen
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import java.util.function.Consumer
// ==========================================
// 1. 基础事件定义优化
// ==========================================
sealed class AppEvent(
open val target: Colleen,
val bubbles: Boolean = true,
) {
// 允许在这个 Bus 节点被修改,标记当前处理事件的对象
var currentTarget: Any? = null
internal set
var isPropagationStopped: Boolean = false
private set
fun stopPropagation() {
isPropagationStopped = true
}
val timestamp: Long = System.currentTimeMillis()
}
// 示例:优化 Transform 逻辑
data class RequestReceived(
override val target: Colleen,
val request: Request,
) : AppEvent(target, false) {
// 使用 List 避免覆盖,或者提供 compose 方法
private val _transforms = ArrayList<(Request) -> Request>()
val transforms: List<(Request) -> Request>
get() = _transforms
fun addTransform(block: (Request) -> Request) {
_transforms.add(block)
}
// 这是一个辅助方法,用来应用所有变换
fun applyTransforms(initial: Request): Request {
var current = initial
_transforms.forEach { current = it(current) }
return current
}
}
// (其他事件定义保持不变...)
// ==========================================
// 2. 监听器与订阅句柄
// ==========================================
/**
* 返回此接口以方便取消订阅
*/
fun interface Subscription {
fun unsubscribe()
}
class EventListener<T : AppEvent>(
val eventType: Class<T>, // 记住类型,优化删除性能
val handler: Consumer<T>,
)
// ==========================================
// 3. EventBus 核心实现
// ==========================================
class EventBus(
// 如果 EventBus 依附于某个组件(如 SubApp),这里应该传入 owner
private val owner: Any? = null
) {
internal var parent: EventBus? = null
// 缓存事件类的继承层级,避免每次 emit 反射。
// Key: 具体事件类 (ServerStarting), Value: [ServerStarting, AppEvent, Object]
private val classHierarchyCache = ConcurrentHashMap<Class<*>, List<Class<*>>>()
@PublishedApi
internal val listeners =
ConcurrentHashMap<Class<*>, CopyOnWriteArrayList<EventListener<*>>>()
/**
* Subscribe
*/
inline fun <reified T : AppEvent> on(noinline handler: (T) -> Unit): Subscription {
return on(T::class.java, Consumer(handler))
}
/**
* Java Compatible Subscribe
*/
fun <T : AppEvent> on(clazz: Class<T>, handler: Consumer<T>): Subscription {
val listener = EventListener(clazz, handler)
// 这里的 Key 使用 clazz,保证监听父类也能存入
listeners.computeIfAbsent(clazz) { CopyOnWriteArrayList() }.add(listener)
return Subscription {
// O(1) 查找,而不是 O(N) 遍历
listeners[clazz]?.remove(listener)
}
}
/**
* Emit with Polymorphism Support
*/
fun emit(event: AppEvent) {
// 1. 设置当前处理节点
if (owner != null) {
event.currentTarget = owner
}
// 2. 获取该事件所有的父类类型 (解决多态 Bug)
val eventTypes = getHierarchy(event::class.java)
// 3. 遍历所有匹配的类型进行分发
// 例如 emit(ServerStarting) -> 会触发 listeners[ServerStarting] 和 listeners[AppEvent]
for (type in eventTypes) {
val typeListeners = listeners[type] ?: continue
typeListeners.forEach { listener ->
@Suppress("UNCHECKED_CAST")
try {
// 双重检查,确保类型安全
if (type.isAssignableFrom(event::class.java)) {
(listener as EventListener<AppEvent>).handler.accept(event)
}
} catch (e: Exception) {
// 实际项目中建议使用具体的 Logger
System.err.println("Error in event listener for ${type.simpleName}: ${e.message}")
e.printStackTrace()
}
}
}
// 4. 冒泡逻辑
if (event.bubbles && !event.isPropagationStopped) {
parent?.emit(event)
}
}
/**
* 辅助方法:获取类及其所有父类/接口 (缓存优化)
* 对于 AppEvent 体系,只需要向上找类即可,如果 AppEvent 实现了接口也想分发,需改为 BFS 搜索接口
*/
private fun getHierarchy(clazz: Class<*>): List<Class<*>> {
return classHierarchyCache.computeIfAbsent(clazz) { cls ->
val hierarchy = ArrayList<Class<*>>()
var current: Class<*>? = cls
while (current != null && AppEvent::class.java.isAssignableFrom(current)) {
hierarchy.add(current)
current = current.superclass
}
hierarchy
}
}
}总结关键变更点:
getHierarchy缓存机制:解决了
on<AppEvent> { }收不到ServerStarting的问题。使用
ConcurrentHashMap缓存反射结果,保证性能。
Subscription接口:取代了低效的
off方法,让注销操作变为 $O(1)$ (哈希查找)。
Transformer列表:将
transform变量改为addTransform方法,允许多个中间件共同修改 Request/Response。
owner属性:如果在创建
EventBus时传入Colleen实例或SubApp实例,currentTarget将会正确反映当前事件流经的层级。