如何实现多个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)是最优解:
- 定义一个注解标记需要生成API结果的接口:
@Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) annotation class GenerateApiResult
- 编写KSP处理器,自动为标记的接口生成包含通用结果和自定义类型的密封接口。比如你只需要定义自定义错误:
@GenerateApiResult sealed interface ApiCallResult1 { data class WrongID(val id: Int) : ApiCallResult1 }
处理器会自动生成Success、UnexpectedError、NetworkError这三个子类,完全消除重复代码。
总结
- 接口数量少(10个以内):选方案1或方案2,代码直观易维护。
- 接口数量多(数十个):选方案3,用代码生成彻底解决重复问题。
- 绝对不要用全局
Everything接口,会破坏类型安全,增加维护负担。
内容的提问来源于stack exchange,提问作者Piotr Śmietana
相关产品推荐
相关产品推荐

