You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现多个Kotlin密封类共享基础子类并支持自定义扩展?

问题

我需要为多个API调用定义结果类型,每个结果都包含通用的成功、网络错误、意外错误类型,同时还要有各自的特定错误(比如ID错误、用户不存在等)。一开始我尝试用接口复用通用类型,但发现通用子类无法被当作具体API结果的实例处理;后来试了全局接口的方案,但维护成本极高,还会导致when表达式的类型提示问题。


解决方案

方案1:密封接口层级+委托复用通用实现

先定义通用的密封接口,包含所有通用结果:

sealed interface BaseApiCallResult {
    data class Success(val data: String) : BaseApiCallResult
    data class UnexpectedError(val error: Throwable) : BaseApiCallResult
    data class NetworkError(val error: ApolloException) : BaseApiCallResult
}

然后每个具体的API结果定义为密封接口,继承BaseApiCallResult,并用委托复用通用子类的实现:

sealed interface ApiCallResult1 : BaseApiCallResult {
    data class WrongID(val id: Int) : ApiCallResult1

    // 通过委托复用Base中的实现,同时成为ApiCallResult1的子类
    data class Success(val data: String) : ApiCallResult1 by BaseApiCallResult.Success(data)
    data class UnexpectedError(val error: Throwable) : ApiCallResult1 by BaseApiCallResult.UnexpectedError(error)
    data class NetworkError(val error: ApolloException) : ApiCallResult1 by BaseApiCallResult.NetworkError(error)
}

这样ApiCallResult1.Success就属于ApiCallResult1的实例,when表达式可以正常处理所有情况。缺点是每个具体接口都要重复声明通用子类,适合接口数量不多的场景。

方案2:泛型密封类+多实现组合

用泛型密封类封装通用结果,然后让具体API结果的子类同时实现通用类和自己的密封接口:

// 通用结果泛型密封类,支持不同API的成功数据类型
sealed class GeneralApiResult<out T> {
    data class Success<out T>(val data: T) : GeneralApiResult<T>()
    data class UnexpectedError(val error: Throwable) : GeneralApiResult<Nothing>()
    data class NetworkError(val error: ApolloException) : GeneralApiResult<Nothing>()
}

// 具体API结果密封接口
sealed interface ApiCallResult1 {
    // 同时实现通用成功类和当前接口
    data class Success(val data: String) : ApiCallResult1, GeneralApiResult.Success<String>(data)
    data class UnexpectedError(val error: Throwable) : ApiCallResult1, GeneralApiResult.UnexpectedError(error)
    data class NetworkError(val error: ApolloException) : ApiCallResult1, GeneralApiResult.NetworkError(error)
    // 自定义错误类型
    data class WrongID(val id: Int) : ApiCallResult1
}

处理结果时,when表达式可以完整覆盖所有分支:

fun handleResult(result: ApiCallResult1) {
    when (result) {
        is ApiCallResult1.Success -> println("成功:${result.data}")
        is ApiCallResult1.UnexpectedError -> println("意外错误:${result.error}")
        is ApiCallResult1.NetworkError -> println("网络错误:${result.error}")
        is ApiCallResult1.WrongID -> println("ID错误:${result.id}")
    }
}

这个方案的类型更清晰,但同样需要每个接口重复声明通用子类。

方案3:代码生成(适合数十个接口的场景)

如果有大量API结果需要定义,用Kotlin代码生成(比如KSP)是最优解:

  1. 定义一个注解标记需要生成API结果的接口:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class GenerateApiResult
  1. 编写KSP处理器,自动为标记的接口生成包含通用结果和自定义类型的密封接口。比如你只需要定义自定义错误:
@GenerateApiResult
sealed interface ApiCallResult1 {
    data class WrongID(val id: Int) : ApiCallResult1
}

处理器会自动生成Success、UnexpectedError、NetworkError这三个子类,完全消除重复代码。


总结
  • 接口数量少(10个以内):选方案1或方案2,代码直观易维护。
  • 接口数量多(数十个):选方案3,用代码生成彻底解决重复问题。
  • 绝对不要用全局Everything接口,会破坏类型安全,增加维护负担。

内容的提问来源于stack exchange,提问作者Piotr Śmietana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 22:10:11