定义事件通用基类:sealed class Event 与派生事件

为什么选择密封类作为事件基类

事件总线的核心是事件类型定义。传统事件总线(如 EventBus)使用 Object 或任意 Any 作为事件载体,导致类型不安全、运行时强制转换、订阅者需要手动检查类型。Kotlin 密封类提供了 受限的类层次,所有事件必须声明在同一个编译单元内,编译器能够穷举所有子类型,搭配 when 表达式实现编译期安全检查。

核心优势:无反射、无字符串标签、无运行时类型擦除——每个事件都是确定的、类型安全的。

密封事件基类定义

定义一个 sealed class Event 作为所有事件的统一基类。派生类使用 data class、object 或 data class 来表达不同复杂度的事件载荷。

// 事件基类
sealed class Event {
    // 防止外部直接继承,所有子类必须在同一文件或嵌套声明
}

// 带用户数据的事件(数据类)
data class LoginEvent(val userId: String, val timestamp: Long) : Event()

// 无参数事件(单例对象)
object LogoutEvent : Event()

// 带泛型载荷的事件
data class DataEvent<T>(val payload: T, val source: String) : Event()
派生类型Kotlin 关键词典型用途内存开销
数据类(data class)data class携带多字段、必须对比相等性的事件(如网络响应、表单提交)每个实例独立
对象类(object)object无额外数据、全局唯一的信号(如退出登录、切换主题)单例,零重复
泛型数据类data class<T>载荷类型可变的事件(如列表刷新、泛型消息)泛型擦除,实例化后与具体类型绑定
实战建议:将 Event 定义为 sealed interface 也可行,但 sealed class 允许在基类中存储通用属性(如事件 ID、分发时间戳),且兼容 Java 互调(密封接口需要 Kotlin 1.5+)。本课程统一使用 sealed class。

密封类层次结构图示

下图展示了事件基类与三种典型派生事件的继承关系,以及编译期穷举检查的工作点:

密封事件基类层次结构 sealed class Event LoginEvent data class userId, timestamp LogoutEvent object 无数据,全局单例 DataEvent<T> data class payload, source 编译期穷举检查点 when 表达式必须覆盖 所有子类型

事件分发的 when 穷举处理

密封类最大的实战价值在于 when 表达式必须是穷举的(exhaustive)。增加新事件类型时,所有使用 when 订阅的地方会产生编译错误,强迫开发者处理新事件,避免遗漏逻辑。

fun handleEvent(event: Event) {
    when (event) {
        is LoginEvent -> {
            // event 已自动智能转换为 LoginEvent
            saveLogin(event.userId, event.timestamp)
        }
        is LogoutEvent -> {
            clearSession()
        }
        is DataEvent<*> -> {
            // 泛型擦除,payload 只能作为 Any 使用
            processPayload(event.payload)
        }
        // 无需 else 分支——编译器证明已穷举
    }
}
注意:当使用 DataEvent<T> 时,由于泛型类型擦除,when 分支中不能直接获取 T 的具体类型。建议在事件内部提供类型判断辅助函数,或者避免使用泛型事件作为唯一区分条件。

通用事件基类的扩展设计

实际项目中,可以在 Event 基类中加入通用字段,例如事件发布时间戳、事件优先级,所有派生类自动继承:

sealed class Event(
    val id: Long = System.currentTimeMillis()   // 每个事件带唯一时间戳ID
) {
    open val priority: Int = 0                   // 默认优先级
}

data class LoginEvent(val userId: String) : Event() {
    override val priority: Int = 10              // 登录事件高优先级
}
基类能力实现方式典型应用
时间戳构造函数默认值日志追踪、事件顺序
事件优先级open val分发队列调度
取消标记var isConsumed防止重复处理
来源标识val source: String事件溯源、调试

通过合理设计基类,可以将事件总线的共性逻辑上移,减少每个派生类的重复代码,同时保持密封类的类型安全特性。