gpt
这是一次已经相当成熟、接近“可对外发布”的事件系统实现。
整体方向是对的,而且你已经明显在有意识地对齐 DOM / koa / 应用层级事件模型。
下面我会非常严格地 review,分为:
明确的 BUG / 语义风险(必须改)
设计层面的隐患(强烈建议改)
并发 / 生命周期 / API 细节优化
“如果这是我自己的框架,我会怎么微调”
我会尽量只挑“真的重要”的点,不做吹毛求疵。
一、明确的问题 / BUG / 不一致(重点)
❌ 1. currentTarget 完全没有被使用(但 API 已经暴露)
var currentTarget: Any? = null
internal set问题:
emit()过程中 从未设置冒泡时也没有更新
listener 看到的
currentTarget永远是null
这在语义上是 不完整实现,甚至是误导 API。
你现在的事件模型中,至少有 3 个“目标概念”:
概念
含义
target
事件源头(origin)
currentTarget
当前处理该事件的 App / Bus
parent
冒泡路径
👉 如果你保留 currentTarget,就必须在 emit 时维护它
否则建议 现在就删掉,等传播模型稳定后再加。
❌ 2. EventBus 与 Colleen 强耦合,但接口没体现
sealed class AppEvent(
open val target: Colleen,
val bubbles: Boolean = true,
)但:
class EventBus {
internal var parent: EventBus? = null
}这里隐含了一个事实:
EventBus 是“挂在 Colleen 上的”,并且 EventBus 树 ≈ App 树
但这个关系是隐式的:
emit()并不知道“我属于哪个 Colleen”currentTarget也没法正确设置
⚠️ 这会在你以后支持:
多 EventBus
App 内子模块
测试 / mock
时变得非常痛苦。
❌ 3. 冒泡逻辑是“事件级别的”,而不是“调度级别的”
sealed class AppEvent(
open val target: Colleen,
val bubbles: Boolean = true,
)你现在是:
事件类型决定是否冒泡
而不是:
这次 emit 是否冒泡
这会导致一个问题:
val e = RequestReceived(app)
// 想在某次 emit 时临时开启冒泡?做不到
bus.emit(e)目前这还勉强可接受,但你要清楚:
这是 类型级策略
不是 实例级策略
如果你未来想支持:
debug / replay / 测试
内部 emit(不冒泡)
用户手动 re-dispatch
👉 你会被这个设计限制住。
(下面第 4 部分我会给你一个“最小侵入”的演进方案)
二、设计层面的隐患(强烈建议)
⚠️ 4. emit() 直接复用同一个 event 对象进行冒泡
parent?.emit(event)风险点:
listener 可以:
stopPropagation()修改
transform
上层 listener 接收到的 event 已经被下层 listener 改写
这在请求处理类事件中尤其危险。
👉 这不是“BUG”,但你要明确这是你想要的语义:
类似 DOM:同一个 Event 实例沿着路径传播
如果是这个目标,你必须补齐:
currentTarget清晰的传播顺序保证
否则就会显得“半吊子”。
⚠️ 5. listener 只能精确匹配 event::class.java
listeners[event::class.java]这意味着:
on<AppEvent> { ... } // 永远收不到任何事件如果这是刻意设计(只允许精确类型),OK
如果不是,那这是一个重大可扩展性限制
📌 很多框架都会至少支持:
精确匹配
父类 / 接口监听(可选)
三、并发 / 生命周期 / API 细节
✅ 6. CopyOnWriteArrayList 用得是对的(👍)
你已经明确写了:
// Listener registration should happen during startup.在这个前提下:
读多写少
顺序稳定
无锁遍历
👉 CopyOnWriteArrayList 是非常合理的选择
⚠️ 7. off() 是 O(n*m),但可接受
listeners.values.forEach { it.remove(listener) }小规模 OK
如果未来 listener 数量变多,可以:
记录 listener → eventType 的反向索引
现在 不用急着优化
⚠️ 8. listener 异常被吞了,但事件继续传播
catch (e: Exception) {
logger.error("Error in event listener: ${e.message}")
}你需要想清楚一个策略问题:
listener 抛异常:
是否中断当前事件?
是否触发
ExceptionThrown事件?是否影响冒泡?
目前是:
静默失败 + 继续传播
这是 OK 的默认,但建议你至少在注释中声明
四、如果是我,我会怎么“最小侵入式”优化
下面这部分不是否定你现在的设计,而是给你一个演进路线。
✅ 1️⃣ 补齐 currentTarget(强烈建议)
fun <T : AppEvent> emit(event: T) {
event.currentTarget = this.owner // EventBus 知道自己属于哪个 Colleen
val eventListeners = listeners[event::class.java] ?: emptyList()
for (listener in eventListeners) {
if (event.isPropagationStopped) break
...
}
if (event.bubbles && !event.isPropagationStopped) {
parent?.emit(event)
}
}这一步会立刻让你的事件系统“完整一档”
✅ 2️⃣ 把 bubbles 语义文档化为「类型级策略」
你现在的设计 不是错,但要明确:
/**
* Whether this event type participates in bubbling.
* This is a semantic property of the event, not dispatch behavior.
*/
val bubbles: Boolean这样以后你自己回来看也不会迷糊。
✅ 3️⃣ 给 EventBus 一个“身份”
class EventBus(
val owner: Colleen
) {
var parent: EventBus? = null
}这会让:
targetcurrentTarget冒泡路径
全部对齐
✅ 4️⃣ (未来)引入 DispatchContext,而不是现在推倒重来
当你将来需要:
capture
async
priority
once
你可以加一个:
class DispatchContext(
val event: AppEvent,
val bubble: Boolean
)而 不需要修改现有 Event 类型
五、总体评价(实话)
这是一个“已经超过 90% 框架作者会写的事件系统”的实现。
优点非常明确:
语义清晰
API 克制
没有过度设计
明显考虑了未来扩展
真正的问题只有一句话:
传播模型已经“呼之欲出”,但还差最后一步抽象对齐
如果你愿意,下一步我可以:
直接帮你画一张 Colleen App / EventBus / Event 传播关系图
或者帮你把它升级成一个 capture → target → bubble 的完整模型(但保持你现在 API 基本不变)
你这个代码,已经值得“认真打磨”了。