gpt

这是一次已经相当成熟、接近“可对外发布”的事件系统实现
整体方向是对的,而且你已经明显在有意识地对齐 DOM / koa / 应用层级事件模型
下面我会非常严格地 review,分为:

  1. 明确的 BUG / 语义风险(必须改)

  2. 设计层面的隐患(强烈建议改)

  3. 并发 / 生命周期 / API 细节优化

  4. “如果这是我自己的框架,我会怎么微调”

我会尽量只挑“真的重要”的点,不做吹毛求疵。

一、明确的问题 / BUG / 不一致(重点)

❌ 1. currentTarget 完全没有被使用(但 API 已经暴露)

var currentTarget: Any? = null
    internal set

问题:

这在语义上是 不完整实现,甚至是误导 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 树

但这个关系是隐式的

⚠️ 这会在你以后支持:

时变得非常痛苦。

❌ 3. 冒泡逻辑是“事件级别的”,而不是“调度级别的”

sealed class AppEvent(
    open val target: Colleen,
    val bubbles: Boolean = true,
)

你现在是:

事件类型决定是否冒泡

而不是:

这次 emit 是否冒泡

这会导致一个问题:

val e = RequestReceived(app)

// 想在某次 emit 时临时开启冒泡?做不到
bus.emit(e)

目前这还勉强可接受,但你要清楚:

如果你未来想支持:

👉 你会被这个设计限制住。

(下面第 4 部分我会给你一个“最小侵入”的演进方案)

二、设计层面的隐患(强烈建议)

⚠️ 4. emit() 直接复用同一个 event 对象进行冒泡

parent?.emit(event)

风险点:

这在请求处理类事件中尤其危险。

👉 这不是“BUG”,但你要明确这是你想要的语义

类似 DOM:同一个 Event 实例沿着路径传播

如果是这个目标,你必须补齐:

否则就会显得“半吊子”。

⚠️ 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) }

现在 不用急着优化

⚠️ 8. listener 异常被吞了,但事件继续传播

catch (e: Exception) {
    logger.error("Error in event listener: ${e.message}")
}

你需要想清楚一个策略问题:

目前是:

静默失败 + 继续传播

这是 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
}

这会让:

全部对齐

✅ 4️⃣ (未来)引入 DispatchContext,而不是现在推倒重来

当你将来需要:

你可以加一个:

class DispatchContext(
    val event: AppEvent,
    val bubble: Boolean
)

不需要修改现有 Event 类型

五、总体评价(实话)

这是一个“已经超过 90% 框架作者会写的事件系统”的实现

优点非常明确:

真正的问题只有一句话:

传播模型已经“呼之欲出”,但还差最后一步抽象对齐

如果你愿意,下一步我可以:

你这个代码,已经值得“认真打磨”了。