根据Kotlin官方文档,密封类代表限定的类层次结构,它提供了对继承的更多控制。当一个类被标记为密封时,它可以有子类,但所有这些子类必须在与密封类本身相同的文件中声明。这使得你在编译时就能知道所有可能的子类。
另一方面,接口更加通用,用于定义类必须满足的约定。它们是一组函数(带有或不带有默认实现),实现接口的类必须提供这些函数。
那么,假设我们的应用将发出一个"UiState"类的StateFlow,并且我们希望限制子类为以下类型:Loading、Main、LoggedIn、LoggedOut、NavigateToUrl(这需要一个String类的参数)。我们应该选择密封类还是接口呢?(注意:我们将枚举排除在讨论之外,因为某些类型需要接受参数)
让我们探索两种选择:
密封类
sealed class UiState {
object Loading : UiState()
object Main: UiState()
object LoggedIn : UiState()
object LoggedOut : UiState()
data class NavigateToUrl(val url: String) : UiState()
}
view raw
接口
interface UiState
object Loading : UiState
object Main : UiState
object LoggedIn : UiState
object LoggedOut : UiState
data class NavigateToUrl(val url: String) : UiState
在这个用例中,你可以看到密封类是明显的赢家。不仅它提供了所有子类必须在同一文件中声明的限制,而且它还提供了详尽的类型检查:当我们使用when表达式来检查一个密封类类型时,编译器知道所有可能的类型并确保所有类型都被覆盖。
fun handleUiState(uiState: UiState) {
when (uiState) {
is UiState.Loading -> {
println("Loading state")
}
is UiState.Main -> {
println("Main state")
}
is UiState.LoggedIn -> {
println("Logged In state")
}
is UiState.LoggedOut -> {
println("Logged Out state")
}
is UiState.NavigateToUrl -> {
println("Navigate to URL: ${uiState.url}")
}
}
}
// Example usage
val currentState: UiState = UiState.NavigateToUrl("https://www.example.com")
handleUiState(currentState)
再来看一个例子,我们的应用API调用需要一个"RetryPolicy",它包含3个属性:numRetries、delayMillis、delayIntervalCoefficient。我们也想实现一个默认策略。哪种方式更好呢?
密封类
sealed class RetryPolicy {
abstract val numRetries: Long
abstract val delayMillis: Long
abstract val delayIntervalCoefficient: Long
data class DefaultRetryPolicy(
override val numRetries: Long = 3,
override val delayMillis: Long = 500,
override val delayIntervalCoefficient: Long = 2
) : RetryPolicy()
}
接口
interface RetryPolicy {
val numRetries: Long
val delayMillis: Long
val delayIntervalCoefficient: Long
}
data class DefaultRetryPolicy(
override val numRetries: Long = 3,
override val delayMillis: Long = 500,
override val delayIntervalCoefficient: Long = 2
) : RetryPolicy
在这种情况下,接口是明显的赢家,因为我们不需要详尽地检查子类,相反,我们可能会创建许多"RetryPolicy"子类,它们可能会在不同的文件或模块中,取决于哪里需要使用它。我们关心的只是我们必须满足"RetryPolicy"类中定义的约定。
结论
那么何时使用密封类 versus 接口呢?
-
• 使用密封类:当你想表示一组固定的类型,并希望在编译时确保所有情况都被处理时(例如,用于在状态机中建模不同的状态)。
-
• 使用接口:当你想定义多个类必须遵守的约定,而不强制执行严格的层次结构时(例如,用于定义不同类之间的共同行为)。