Appearance
07. Kotlin 密封类(Sealed Class)
目录
- Sealed Class 定义和语法
- Sealed Class 的核心特性
- Sealed Class vs Enum 对比
- Sealed Class vs Sealed Interface
- when 表达式的 exhaustive 特性
- 状态管理中的应用
- 结果封装(Result/Data 类替代)
- 高级应用场景
- 最佳实践
- 常见错误与陷阱
- 性能优化
- 面试考点
1. Sealed Class 定义和语法
1.1 什么是密封类
密封类(Sealed Class) 是 Kotlin 1.1 版本引入的一种类层次结构限制。它是一种特殊的类,用于表示受限的类层次结构——即你能够知道一个密封类的所有可能的子类。
kotlin
// 基本定义语法
sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()1.2 关键约束
密封类有以下重要约束:
- 所有子类必须在同一个文件中定义(包含内部类)
- 密封类不能是 open 或 abstract(默认就是抽象的)
- 不能从外部文件继承密封类
- 密封类可以有构造器参数、属性和方法
kotlin
// ✅ 正确:所有子类在同一文件
sealed class Animal {
data class Dog(val name: String, val age: Int) : Animal()
data class Cat(val name: String, val color: String) : Animal()
object Unknown : Animal()
}
// ❌ 错误:不能在其他文件继承
// 在其他文件中
class Bird : Animal() // 编译错误!
// ✅ 正确:在密封类内部定义子类
sealed class Shape {
class Circle(val radius: Double) : Shape()
class Rectangle(val width: Double, val height: Double) : Shape()
class Triangle(val a: Double, val b: Double, val c: Double) : Shape()
}1.3 密封类的继承体系
密封类可以形成多层次的继承体系:
kotlin
sealed class Expression
sealed class Constant(val value: Int) : Expression()
sealed class Operator(val left: Expression, val right: Expression) : Expression()
class Number(val num: Int) : Constant(num)
class BinaryOperator(val left: Expression, val right: Expression) : Operator(left, right)
class Plus(left: Expression, right: Expression) : BinaryOperator(left, right)
class Minus(left: Expression, right: Expression) : BinaryOperator(left, right)1.4 密封类作为构造器参数
kotlin
sealed class Permission {
object Read : Permission()
object Write : Permission()
object Execute : Permission()
}
class User(val name: String, val permission: Permission) {
fun checkAccess(): Boolean = when (permission) {
is Permission.Read -> true
is Permission.Write -> false
is Permission.Execute -> true
}
}2. Sealed Class 的核心特性
2.1 受限的类层次结构
密封类的核心特性是编译器知道所有可能的子类,这使得:
kotlin
sealed class NetworkState
data class Loading(val progress: Int) : NetworkState()
data class Success(val data: List<String>) : NetworkState()
data class Error(val exception: Exception) : NetworkState()
object Idle : NetworkState()
fun processState(state: NetworkState) {
// 编译器知道所有情况,不需要 when 的默认分支
when (state) {
is Loading -> println("Loading: ${state.progress}%")
is Success -> println("Success: ${state.data.size} items")
is Error -> println("Error: ${state.exception.message}")
Idle -> println("Idle state")
}
// 如果添加新的子类,编译器会报错:when 表达式不是 exhaustive
}2.2 作为 when 表达式
kotlin
sealed class UIState<T> {
data class Initial() : UIState<Nothing>()
data class Loading(val progress: Int) : UIState<Nothing>()
data class Success(val data: T) : UIState<T>()
data class Error(val message: String) : UIState<Nothing>()
}
fun <T> renderState(state: UIState<T>) {
when (state) {
is UIState.Initial -> showInitialScreen()
is UIState.Loading -> showLoadingScreen(state.progress)
is UIState.Success -> showDataScreen(state.data)
is UIState.Error -> showErrorScreen(state.message)
}
}2.3 携带数据的能力
与枚举相比,密封类的每个子类都可以携带不同的数据:
kotlin
// 枚举无法携带不同类型的数据
enum class SimpleState {
LOADING, SUCCESS, ERROR
}
// 密封类每个子类可以有自己的属性
sealed class RichState {
data class Loading(val progress: Int, val currentStep: String) : RichState()
data class Success<T>(val data: T, val timestamp: Long) : RichState()
data class Error(val code: Int, val message: String, val retryCount: Int) : RichState()
}3. Sealed Class vs Enum 对比
3.1 核心区别
| 特性 | Enum | Sealed Class |
|---|---|---|
| 实例数量 | 固定(单例) | 可变(可创建多个实例) |
| 携带数据 | 所有实例相同结构 | 每个子类不同结构 |
| 构造函数 | 只能一个 | 每个子类独立 |
| when 表达式 | exhaustive | exhaustive |
| 继承 | 不支持 | 支持多层继承 |
| 性能 | 更优 | 略低(对象创建) |
3.2 使用场景对比
场景 1:固定状态集合(适合 Enum)
kotlin
// 使用枚举
enum class OrderStatus {
PENDING,
CONFIRMED,
SHIPPED,
DELIVERED,
CANCELLED
}
fun getStatusText(status: OrderStatus) = when (status) {
OrderStatus.PENDING -> "待处理"
OrderStatus.CONFIRMED -> "已确认"
OrderStatus.SHIPPED -> "已发货"
OrderStatus.DELIVERED -> "已送达"
OrderStatus.CANCELLED -> "已取消"
}场景 2:状态需要携带数据(适合 Sealed Class)
kotlin
// 使用密封类
sealed class OrderStatus {
object Pending : OrderStatus()
data class Confirmed(val orderNumber: String, val confirmTime: Long) : OrderStatus()
data class Shipped(val trackingNumber: String, val carrier: String) : OrderStatus()
data class Delivered(val deliveryTime: Long, val receiver: String) : OrderStatus()
data class Cancelled(val reason: String, val refundAmount: Double) : OrderStatus()
}
fun getStatusText(status: OrderStatus): String = when (status) {
is OrderStatus.Pending -> "待处理"
is OrderStatus.Confirmed -> "已确认:订单号 ${status.orderNumber}"
is OrderStatus.Shipped -> "已发货:运单号 ${status.trackingNumber}"
is OrderStatus.Delivered -> "已送达:收件人 ${status.receiver}"
is OrderStatus.Cancelled -> "已取消:${status.reason}"
}3.3 何时选择 Enum vs Sealed Class
kotlin
// ✅ 使用 Enum 的场景
// 1. 状态固定且不需要携带数据
enum class WeekDay {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
// 2. 需要单例保证
enum class SingletonService {
INSTANCE;
fun doWork() { /* ... */ }
}
// 3. 需要按顺序遍历
enum class Priority(val level: Int) {
LOW(1), MEDIUM(2), HIGH(3), CRITICAL(4);
fun next() = values().indexOf(this).let { if (it < values().size - 1) values()[it + 1] else null }
}
// ✅ 使用 Sealed Class 的场景
// 1. 状态需要携带不同结构的数据
sealed class FormState {
object Idle : FormState()
data class Validating(val fieldName: String) : FormState()
data class Valid(val form: FormData) : FormState()
data class Invalid(val errors: Map<String, String>) : FormState()
}
// 2. 需要继承层次结构
sealed class Notification {
sealed class System : Notification() {
object Update : System()
data class Security(val level: Int) : System()
}
sealed class User : Notification() {
data class Message(val from: String, val text: String) : User()
data class Mention(val from: String, val context: String) : User()
}
}
// 3. 每个状态需要不同的行为
sealed class PaymentMethod {
data class CreditCard(val number: String, val expiry: String) : PaymentMethod() {
fun validate() = number.length == 16
}
data class PayPal(val email: String) : PaymentMethod() {
fun validate() = email.contains("@")
}
data class Crypto(val wallet: String) : PaymentMethod() {
fun validate() = wallet.startsWith("0x")
}
}4. Sealed Class vs Sealed Interface
4.1 Kotlin 1.9+ 的 Sealed Interface
从 Kotlin 1.9 开始,支持 expect 和 actual 机制下的密封接口,但更常见的是使用 sealed interface(在 Kotlin 多平台中)。
kotlin
// Kotlin 多平台中的密封接口
sealed interface PlatformResult<T> {
data class Success<T>(val data: T) : PlatformResult<T>
data class Failure<T>(val error: Throwable) : PlatformResult<T>
}4.2 Sealed Class 与 Interface 对比
kotlin
// Interface - 开放的层次结构
interface Shape {
fun area(): Double
}
class Circle(val radius: Double) : Shape {
override fun area() = Math.PI * radius * radius
}
class Rectangle(val width: Double, val height: Double) : Shape {
override fun area() = width * height
}
// 外部可以继续添加实现
class Triangle(val base: Double, val height: Double) : Shape {
override fun area() = base * height / 2
}
// Sealed Class - 封闭的层次结构
sealed class ClosedShape {
abstract fun area(): Double
class Circle(val radius: Double) : ClosedShape() {
override fun area() = Math.PI * radius * radius
}
class Rectangle(val width: Double, val height: Double) : ClosedShape() {
override fun area() = width * height
}
}
// 无法在外部添加新的 ClosedShape 子类4.3 选择建议
| 场景 | 推荐 |
|---|---|
| 需要多模块扩展 | Interface |
| 同一模块内完整覆盖 | Sealed Class |
| 需要依赖注入 | Interface |
| 当状态/结果类型 | Sealed Class |
| 多平台项目 | Sealed Interface |
5. when 表达式的 exhaustive 特性
5.1 编译期类型安全
密封类的核心优势是在 when 表达式中提供编译期的类型安全性:
kotlin
sealed class ValidationResult {
object Valid : ValidationResult()
data class Invalid(val errors: List<String>) : ValidationResult()
}
// ✅ 编译器知道所有情况,不需要 else
fun validate(result: ValidationResult) {
when (result) {
is ValidationResult.Valid -> {
// 处理有效结果
}
is ValidationResult.Invalid -> {
// 处理错误
}
}
// 添加新子类时会编译失败,强制处理所有情况
}5.2 省略 is 检查
当密封类的子类没有构造函数参数且是对象时,可以直接引用:
kotlin
sealed class Response<T> {
data class Loading<T> : Response<T>()
data class Success<T>(val data: T) : Response<T>()
data class Failure<T>(val error: String) : Response<T>()
}
fun handleResponse(response: Response<String>) {
when (response) {
Response.Loading -> println("加载中...") // 无需 is
is Response.Success -> println("数据:${response.data}")
is Response.Failure -> println("错误:${response.error}")
}
}5.3 嵌套密封类的 exhaustiveness
kotlin
sealed class Color {
sealed class Primary : Color() {
object Red : Primary()
object Green : Primary()
object Blue : Primary()
}
sealed class Secondary : Color() {
object Orange : Secondary()
object Purple : Secondary()
object Brown : Secondary()
}
object Black : Color()
object White : Color()
}
fun getColorType(color: Color) = when (color) {
is Color.Primary -> "主色"
is Color.Secondary -> "副色"
Color.Black -> "黑色"
Color.White -> "白色"
}
// 或者更细粒度
fun getColorDetail(color: Color) = when (color) {
Color.Primary.Red -> "红色"
Color.Primary.Green -> "绿色"
Color.Primary.Blue -> "蓝色"
Color.Secondary.Orange -> "橙色"
Color.Secondary.Purple -> "紫色"
Color.Secondary.Brown -> "棕色"
Color.Black -> "黑色"
Color.White -> "白色"
}5.4 智能转换
kotlin
sealed class UserAction {
data class Login(val username: String, val password: String) : UserAction()
data class Logout(val sessionId: String) : UserAction()
data class UpdateProfile(val email: String, val avatar: String) : UserAction()
object DeleteAccount : UserAction()
}
fun processAction(action: UserAction) {
when (action) {
is UserAction.Login -> {
// 这里 action.username 可以直接访问,智能转换
println("登录用户:${action.username}")
}
is UserAction.Logout -> {
println("登出会话:${action.sessionId}")
}
is UserAction.UpdateProfile -> {
println("更新邮箱:${action.email}")
}
UserAction.DeleteAccount -> {
println("删除账户")
}
}
}6. 状态管理中的应用
6.1 UI 状态管理
kotlin
// 用户界面状态
sealed class ScreenState<T> {
// 初始状态
object Initial : ScreenState<Nothing>()
// 加载状态
data class Loading(val progress: Int = 0, val showIndicator: Boolean = true) : ScreenState<Nothing>()
// 成功状态
data class Success<T>(val data: T, val isLoadingMore: Boolean = false) : ScreenState<T>()
// 错误状态
data class Error<T>(
val message: String,
val exception: Throwable? = null,
val canRetry: Boolean = true,
val retryCount: Int = 0
) : ScreenState<Nothing>()
// 空状态
data class Empty<T>(val hint: String = "") : ScreenState<Nothing>()
// 无网络状态
object NoNetwork : ScreenState<Nothing>()
}
// 使用示例
class UserScreenViewModel : ViewModel() {
private val _uiState = MutableLiveData<ScreenState<User>>(ScreenState.Initial)
val uiState: LiveData<ScreenState<User>> = _uiState
fun loadUser(id: String) {
_uiState.value = ScreenState.Loading(0, true)
lifecycleScope.launch {
try {
val user = userRepository.findById(id)
_uiState.value = ScreenState.Success(user)
} catch (e: NoNetworkException) {
_uiState.value = ScreenState.NoNetwork
} catch (e: Exception) {
_uiState.value = ScreenState.Error(
message = e.message ?: "未知错误",
exception = e,
canRetry = true,
retryCount = 0
)
}
}
}
}
// UI 渲染
fun renderScreenState(state: ScreenState<User>) {
when (state) {
is ScreenState.Initial -> showNothing()
is ScreenState.Loading -> {
showProgressBar(state.progress)
if (state.showIndicator) showLoadingIndicator()
}
is ScreenState.Success -> {
showUserData(state.data)
if (state.isLoadingMore) showLoadingFooter()
}
is ScreenState.Error -> {
showErrorView(state.message)
if (state.canRetry) showRetryButton()
}
is ScreenState.Empty -> showEmptyView(state.hint)
ScreenState.NoNetwork -> showNoNetworkView()
}
}6.2 网络状态管理
kotlin
sealed class NetworkCallState<T> {
data class NotStarted<T>() : NetworkCallState<T>()
data class InProgress<T>(val progress: Float = 0f) : NetworkCallState<T>()
data class Completed<T>(val result: T, val duration: Long) : NetworkCallState<T>()
data class Failed<T>(val error: NetworkError, val attempt: Int) : NetworkCallState<T>()
fun isIdle() = this is NotStarted
fun isRunning() = this is InProgress
fun isSuccess() = this is Completed
fun isFailed() = this is Failed
}
sealed class NetworkError {
data class Timeout(val timeout: Long) : NetworkError()
data class ServerError(val code: Int, val message: String) : NetworkError()
data class ClientError(val code: Int, val message: String) : NetworkError()
object NoInternet : NetworkError()
data class ParseError(val rawResponse: String) : NetworkError()
}
// 网络请求封装
suspend fun <T> safeApiCall(
apiCall: suspend () -> T,
timeout: Long = 30000L
): NetworkCallState<T> {
val startTime = System.currentTimeMillis()
return try {
withTimeout(timeout) {
val result = apiCall()
NetworkCallState.Completed(result, System.currentTimeMillis() - startTime)
}
} catch (e: TimeoutCancellationException) {
NetworkCallState.Failed(NetworkError.Timeout(timeout), 1)
} catch (e: HttpException) {
when (e.code()) {
in 400..499 -> NetworkCallState.Failed(
NetworkError.ClientError(e.code(), e.message()),
1
)
in 500..599 -> NetworkCallState.Failed(
NetworkError.ServerError(e.code(), e.message()),
1
)
else -> NetworkCallState.Failed(
NetworkError.ServerError(e.code(), "未知错误"),
1
)
}
} catch (e: NoNetworkException) {
NetworkCallState.Failed(NetworkError.NoInternet, 1)
} catch (e: Exception) {
NetworkCallState.Failed(NetworkError.ParseError(e.toString()), 1)
}
}6.3 表单状态管理
kotlin
sealed class FormFieldState {
object Idle : FormFieldState()
object Validating : FormFieldState()
object Valid : FormFieldState()
data class Invalid(val error: String) : FormFieldState()
}
sealed class FormState {
data class Initial(
val emailState: FormFieldState = FormFieldState.Idle,
val passwordState: FormFieldState = FormFieldState.Idle,
val nameState: FormFieldState = FormFieldState.Idle
) : FormState() {
fun isValid() = emailState is FormFieldState.Valid &&
passwordState is FormFieldState.Valid &&
nameState is FormFieldState.Valid
}
object Submitting : FormState()
data class Submitted(val success: Boolean, val message: String? = null) : FormState()
}
// 表单 ViewModel
class LoginFormViewModel : ViewModel() {
private val _formState = MutableLiveData<FormState>(FormState.Initial())
val formState: LiveData<FormState> = _formState
fun setEmail(value: String) {
val currentState = (formState.value as? FormState.Initial) ?: return
val newState = when {
value.isEmpty() -> FormState.Invalid("邮箱不能为空")
!value.contains("@") -> FormState.Invalid("邮箱格式不正确")
else -> FormState.Valid
}
_formState.value = FormState.Initial(
emailState = newState,
passwordState = currentState.passwordState,
nameState = currentState.nameState
)
}
fun submit() {
val currentState = _formState.value as? FormState.Initial ?: return
if (!currentState.isValid()) return
_formState.value = FormState.Submitting
lifecycleScope.launch {
try {
repository.login(currentState.email, currentState.password)
_formState.value = FormState.Submitted(true, "登录成功")
} catch (e: Exception) {
_formState.value = FormState.Submitted(false, e.message)
}
}
}
}6.4 复杂业务状态
kotlin
// 订单状态流转
sealed class OrderWorkflow {
sealed class Draft : OrderWorkflow() {
data class Editing(val items: List<CartItem>) : Draft()
object Reviewing : Draft()
}
sealed class Processing : OrderWorkflow() {
object PaymentPending : Processing()
data class PaymentProcessing(val paymentMethod: String) : Processing()
object InventoryChecking : Processing()
object Packaging : Processing()
}
sealed class Shipped : OrderWorkflow() {
data class InTransit(val trackingNumber: String, val carrier: String) : Shipped()
object Delivered : Shipped()
}
sealed class Completed : OrderWorkflow() {
data class Confirmed(val review: Rating? = null) : Completed()
object Refunded : Completed()
object Cancelled : Completed()
}
// 状态转换
fun canTransitionTo(next: OrderWorkflow): Boolean = when {
this is Draft && next is Processing -> true
this is Processing && next is Shipped -> true
this is Shipped && next is Completed -> true
else -> false
}
// 状态进度计算
val progress: Float
get() = when (this) {
is Draft -> 0.2f
is Processing -> 0.5f
is Shipped -> 0.8f
is Completed -> 1.0f
}
}
fun renderOrderStatus(status: OrderWorkflow) {
when (status) {
is OrderWorkflow.Draft -> {
when (status) {
is OrderWorkflow.Draft.Editing -> showDraftEditing(status.items)
OrderWorkflow.Draft.Reviewing -> showDraftReviewing()
}
}
is OrderWorkflow.Processing -> {
when (status) {
OrderWorkflow.Processing.PaymentPending -> showPaymentPending()
is OrderWorkflow.Processing.PaymentProcessing ->
showPaymentProcessing(status.paymentMethod)
OrderWorkflow.Processing.InventoryChecking -> showInventoryChecking()
OrderWorkflow.Processing.Packaging -> showPackaging()
}
}
is OrderWorkflow.Shipped -> {
when (status) {
is OrderWorkflow.Shipped.InTransit -> showInTransit(status.trackingNumber, status.carrier)
OrderWorkflow.Shipped.Delivered -> showDelivered()
}
}
is OrderWorkflow.Completed -> {
when (status) {
is OrderWorkflow.Completed.Confirmed -> showConfirmed(status.review)
OrderWorkflow.Completed.Refunded -> showRefunded()
OrderWorkflow.Completed.Cancelled -> showCancelled()
}
}
}
}7. 结果封装(Result/Data 类替代)
7.1 自定义 Result 类型
kotlin
// 标准 Result 的增强版
sealed class Result<T> {
data class Success<T>(val data: T) : Result<T>()
data class Failure<T>(val error: Throwable, val recoverable: Boolean = true) : Result<T>()
data class Loading<T>(val progress: Int = 0, val currentStep: String = "") : Result<T>()
// 便捷方法
fun <R> map(transform: (T) -> R): Result<R> = when (this) {
is Success -> Success(transform(data))
is Failure -> Failure(error, recoverable)
is Loading -> Loading(progress, currentStep)
}
fun <R> mapError(transform: (Throwable) -> Throwable): Result<T> = when (this) {
is Failure -> Failure(transform(error), recoverable)
else -> this
}
fun getOrNull(): T? = (this as? Success)?.data
fun fold(
onSuccess: (T) -> Unit,
onFailure: (Throwable) -> Unit,
onLoading: () -> Unit = {}
) {
when (this) {
is Success -> onSuccess(data)
is Failure -> onFailure(error)
is Loading -> onLoading()
}
}
}
// 使用示例
suspend fun fetchUser(id: String): Result<User> = try {
val user = api.getUser(id)
Result.Success(user)
} catch (e: Exception) {
Result.Failure(e)
}
// 链式调用
suspend fun fetchUserDetails(userId: String): Result<UserDetails> {
return fetchUser(userId)
.map { user ->
api.getUserDetails(user.id)
}
.mapError { e ->
UserFetchException("Failed to fetch user: ${e.message}")
}
}
// 组合多个 Result
suspend fun fetchUserProfile(userId: String): Result<ProfileData> {
val userResult = fetchUser(userId)
val detailsResult = fetchUserDetails(userId)
val preferencesResult = fetchUserPreferences(userId)
return when {
userResult is Result.Success &&
detailsResult is Result.Success &&
preferencesResult is Result.Success -> {
Result.Success(ProfileData(
user = userResult.data,
details = detailsResult.data,
preferences = preferencesResult.data
))
}
else -> {
val errors = buildList {
(userResult as? Result.Failure)?.error?.let { add(it) }
(detailsResult as? Result.Failure)?.error?.let { add(it) }
(preferencesResult as? Result.Failure)?.error?.let { add(it) }
}
Result.Failure(MultipleErrorsException(errors))
}
}
}7.2 替代传统 Success/Error 类
kotlin
// 传统方式
class UserResponse {
var success: Boolean = false
var data: User? = null
var errorCode: Int = 0
var errorMessage: String = ""
}
// 密封类方式
sealed class UserResponse {
data class Success(val user: User, val cached: Boolean = false) : UserResponse()
data class Error(val code: Int, val message: String, val details: String? = null) : UserResponse()
object NotFound : UserResponse()
object Unauthorized : UserResponse()
fun isSuccess() = this is Success
fun isError() = this is Error || this is NotFound || this is Unauthorized
fun getErrorOrNull() = when (this) {
is Error -> ApiError(code, message, details)
NotFound -> ApiError(404, "用户未找到")
Unauthorized -> ApiError(401, "未授权")
is Success -> null
}
}
// 解析网络响应
fun parseUserResponse(response: HttpResponse): UserResponse {
return when (response.code) {
200 -> {
val user = parseUser(response.body)
UserResponse.Success(user, response.isCached)
}
404 -> UserResponse.NotFound
401 -> UserResponse.Unauthorized
in 400..599 -> UserResponse.Error(
response.code,
response.body.message,
response.body.details
)
else -> UserResponse.Error(500, "未知错误")
}
}
// UI 处理
fun handleUserResponse(response: UserResponse) {
when (response) {
is UserResponse.Success -> {
showUser(response.user)
if (response.cached) showToast("显示缓存数据")
}
is UserResponse.Error -> {
showError(response.message)
if (response.code == 401) navigateToLogin()
}
UserResponse.NotFound -> {
showNotFound()
}
UserResponse.Unauthorized -> {
showUnauthorized()
}
}
}7.3 多层嵌套结果
kotlin
// 复杂业务场景的结果封装
sealed class PaymentResult {
// 成功
sealed class Success : PaymentResult() {
data class Approved(val transactionId: String, val amount: BigDecimal) : Success()
data class Pending(val referenceId: String, val expiresAt: Instant) : Success()
data class Partial(
val approvedAmount: BigDecimal,
val declinedAmount: BigDecimal,
val transactionId: String
) : Success()
}
// 失败
sealed class Failure : PaymentResult() {
sealed class Rejected : Failure() {
object InsufficientFunds : Rejected()
object CardExpired : Rejected()
object InvalidCardNumber : Rejected()
object FraudDetected : Rejected()
object TooManyAttempts : Rejected()
data class BankError(val code: String, val message: String) : Rejected()
}
sealed class Technical : Failure() {
object Timeout : Technical()
object GatewayUnavailable : Technical()
object NetworkError : Technical()
data class SystemError(val code: String, val recoverable: Boolean) : Technical()
}
}
// 重试建议
fun shouldRetry(): Boolean = when (this) {
is Failure.Technical.Timeout -> true
is Failure.Technical.GatewayUnavailable -> true
is Failure.Technical.NetworkError -> true
is Failure.Technical.SystemError -> recoverable
else -> false
}
// 用户提示
fun userMessage(): String = when (this) {
is Success.Approved -> "支付成功:¥$amount"
is Success.Pending -> "支付处理中"
is Success.Partial -> "部分支付成功"
is Failure.Rejected.InsufficientFunds -> "余额不足"
is Failure.Rejected.CardExpired -> "卡片已过期"
is Failure.Rejected.InvalidCardNumber -> "卡号无效"
is Failure.Rejected.FraudDetected -> "支付被风控拦截"
is Failure.Rejected.TooManyAttempts -> "尝试次数过多"
is Failure.Technical.Timeout -> "请求超时"
is Failure.Technical.GatewayUnavailable -> "支付网关不可用"
is Failure.Technical.NetworkError -> "网络错误"
is Failure.Technical.SystemError -> "系统错误"
}
}8. 高级应用场景
8.1 命令模式实现
kotlin
sealed class Command {
// 导航命令
sealed class Navigate : Command() {
data class ToScreen(val screen: String, val args: Bundle = Bundle()) : Navigate()
object Back : Navigate()
object PopToRoot : Navigate()
}
// 数据操作命令
sealed class DataOperation : Command() {
data class FetchData(val endpoint: String, val cachePolicy: CachePolicy) : DataOperation()
data class UpdateData(val entityId: String, val updates: Map<String, Any>) : DataOperation()
data class DeleteData(val entityId: String, val confirm: Boolean = false) : DataOperation()
}
// UI 反馈命令
sealed class UIFeedback : Command() {
data class ShowToast(val message: String, val duration: Int = Toast.LENGTH_SHORT) : UIFeedback()
data class ShowSnackbar(val message: String, val action: String? = null) : UIFeedback()
data class ShowDialog(val title: String, val message: String, val actions: List<DialogAction>) : UIFeedback()
object ShowProgress : UIFeedback()
object HideProgress : UIFeedback()
}
// 侧边效应命令
sealed class SideEffect : Command() {
data class TrackEvent(val eventName: String, val properties: Map<String, Any> = emptyMap()) : SideEffect()
data class Analytics(val type: AnalyticsType, val data: Any) : SideEffect()
data class CacheData(val key: String, val value: Any, val expiry: Long) : SideEffect()
}
}
// 命令处理器
class CommandDispatcher(
private val navigator: Navigator,
private val repository: Repository,
private val analytics: AnalyticsManager
) {
fun dispatch(command: Command) {
when (command) {
is Command.Navigate.ToScreen -> navigator.navigate(command.screen, command.args)
is Command.Navigate.Back -> navigator.popBackStack()
is Command.Navigate.PopToRoot -> navigator.popBackStackToRoot()
is Command.DataOperation.FetchData -> {
lifecycleScope.launch {
repository.fetch(command.endpoint, command.cachePolicy)
}
}
is Command.DataOperation.UpdateData -> {
repository.update(command.entityId, command.updates)
}
is Command.DataOperation.DeleteData -> {
if (command.confirm) {
repository.delete(command.entityId)
}
}
is Command.UIFeedback.ShowToast -> {
Toast.makeText(context, command.message, command.duration).show()
}
is Command.UIFeedback.ShowDialog -> {
showDialog(command.title, command.message, command.actions)
}
is Command.SideEffect.TrackEvent -> {
analytics.track(command.eventName, command.properties)
}
is Command.SideEffect.CacheData -> {
cache.save(command.key, command.value, command.expiry)
}
}
}
}8.2 状态机实现
kotlin
sealed class State {
abstract val id: String
abstract fun handle(event: Event): Transition
data class Idle(val id: String = "idle") : State() {
override fun handle(event: Event): Transition = when (event) {
is Event.Start -> Transition(Loading, "用户点击开始")
else -> Transition(this, "无效事件")
}
}
data class Loading(val id: String = "loading") : State() {
override fun handle(event: Event): Transition = when (event) {
is Event.DataLoaded -> Transition(Success, "数据加载完成")
is Event.Error -> Transition(Error, "发生错误:${event.message}")
is Event.Cancel -> Transition(Idle, "用户取消")
else -> Transition(this, "无效事件")
}
}
data class Success(val data: Any, val id: String = "success") : State() {
override fun handle(event: Event): Transition = when (event) {
is Event.Reset -> Transition(Idle, "重置到初始状态")
is Event.Refresh -> Transition(Loading, "刷新数据")
else -> Transition(this, "无效事件")
}
}
data class Error(val message: String, val id: String = "error") : State() {
override fun handle(event: Event): Transition = when (event) {
is Event.Retry -> Transition(Loading, "重试")
is Event.Ignore -> Transition(Idle, "忽略错误")
else -> Transition(this, "无效事件")
}
}
}
sealed class Event {
object Start : Event()
object DataLoaded : Event()
data class Error(val message: String) : Event()
object Cancel : Event()
object Reset : Event()
object Refresh : Event()
object Retry : Event()
object Ignore : Event()
}
data class Transition(val nextState: State, val reason: String)
class StateMachine(private val initialState: State = State.Idle()) {
private var currentState: State = initialState
fun handle(event: Event): State {
val transition = currentState.handle(event)
println("状态转换:${currentState.id} -> ${transition.nextState.id} (原因:${transition.reason})")
currentState = transition.nextState
return currentState
}
fun getState(): State = currentState
}8.3 领域驱动设计 (DDD) 中的应用
kotlin
// 聚合根
sealed class OrderStatus {
sealed class Draft : OrderStatus() {
object Created : Draft()
data class ItemAdded(val productId: String, val quantity: Int) : Draft()
data class ItemRemoved(val productId: String) : Draft()
}
sealed class Confirmed : OrderStatus() {
object PaymentPending : Confirmed()
object PaymentSuccess : Confirmed()
}
sealed class Processing : OrderStatus() {
object InventoryAllocated : Processing()
object Packed : Processing()
}
sealed class Shipped : OrderStatus() {
data class CarrierPicked(val carrier: String, val trackingNumber: String) : Shipped()
object InTransit : Shipped()
object OutForDelivery : Shipped()
}
sealed class Completed : OrderStatus() {
object Delivered : Completed()
object Returned : Completed()
object Refunded : Completed()
}
// 状态验证规则
abstract fun canTransitionTo(next: OrderStatus): Boolean
abstract fun transitionRules(): List<String>
}
// 使用状态机的订单
class Order(
val id: String,
private var status: OrderStatus = OrderStatus.Draft.Created
) {
fun apply(event: OrderEvent): Result<String> {
return when (event) {
is OrderEvent.ItemAdded -> {
when (status) {
is OrderStatus.Draft -> {
status = OrderStatus.Draft.ItemAdded(event.productId, event.quantity)
Result.Success("商品已添加")
}
else -> Result.Error("订单已确认,不能添加商品")
}
}
is OrderEvent.PaymentCompleted -> {
when (status) {
is OrderStatus.Draft, is OrderStatus.Confirmed -> {
status = OrderStatus.Confirmed.PaymentSuccess
Result.Success("支付成功")
}
else -> Result.Error("订单状态不允许支付")
}
}
// ... 更多事件处理
}
}
fun getStatus(): OrderStatus = status
}9. 最佳实践
9.1 命名规范
kotlin
// ✅ 好的命名
sealed class AuthenticationState {
object Unauthenticated : AuthenticationState()
object Authenticating : AuthenticationState()
data class Authenticated(val token: String, val expiry: Date) : AuthenticationState()
data class AuthFailed(val reason: AuthenticationError) : AuthenticationState()
}
// ❌ 不好的命名
sealed class Auth { // 太简单,含义不明确
object UNAUTH : Auth() // 大小写不规范
object AUTHENTICATING : Auth()
data class AUTH(val TOKEN: String) : Auth() // 属性命名混乱
}
// ✅ 推荐的命名模式
sealed class Resource<out T> { // 使用泛型上界
data class Idle<out T>() : Resource<T>()
data class Loading<out T>(val progress: Int) : Resource<T>()
data class Success<out T>(val data: T) : Resource<T>()
data class Failure<out T>(val exception: Throwable) : Resource<T>()
}9.2 使用 object vs data class
kotlin
// 没有数据的子类使用 object
sealed class MenuState {
object Closed : MenuState() // ✅ 无参数,使用 object
object Opening : MenuState()
object Open : MenuState()
object Closing : MenuState()
}
// 有数据的子类使用 data class
sealed class SearchResult {
data class Loading(val query: String) : SearchResult() // ✅ 有参数
data class Success(val items: List<Item>) : SearchResult()
data class Error(val message: String) : SearchResult()
data class NoResults(val query: String) : SearchResult()
}
// 混合使用
sealed class ViewState {
object Initial : ViewState()
data class Loading(val items: List<Item> = emptyList()) : ViewState() // 有默认值
data class Success(val items: List<Item>, val hasNext: Boolean) : ViewState()
data class Error(val error: String, val canRetry: Boolean = true) : ViewState()
}9.3 避免过度嵌套
kotlin
// ❌ 过度嵌套
sealed class UserState {
sealed class AuthState : UserState() {
sealed class LoginState : AuthState() {
object Idle : LoginState()
object LoggingIn : LoginState()
data class Success(val user: User) : LoginState()
data class Error(val error: String) : LoginState()
}
sealed class RegisterState : AuthState() {
object Idle : RegisterState()
data class FillingForm(val form: RegisterForm) : RegisterState()
data class Verifying(val email: String) : RegisterState()
data class Success(val user: User) : RegisterState()
data class Error(val error: String) : RegisterState()
}
}
}
// ✅ 扁平化
sealed class UserState {
object AuthIdle : UserState()
data class LoggingIn(val progress: Int) : UserState()
data class LoginSuccess(val user: User) : UserState()
data class LoginError(val error: String) : UserState()
object RegisterIdle : UserState()
data class RegisteringForm(val form: RegisterForm) : UserState()
data class RegisterVerifying(val email: String) : UserState()
data class RegisterSuccess(val user: User) : UserState()
data class RegisterError(val error: String) : UserState()
}9.4 提供便捷方法
kotlin
sealed class NetworkResult<out T> {
data class Success<out T>(val data: T) : NetworkResult<T>()
data class Error(val exception: Throwable, val code: Int? = null) : NetworkResult<Nothing>()
object Loading : NetworkResult<Nothing>()
// 便捷转换
fun <R> map(transform: (T) -> R): NetworkResult<R> = when (this) {
is Success -> Success(transform(data))
is Error -> Error(exception, code)
Loading -> Loading
}
fun <R> flatMap(transform: (T) -> NetworkResult<R>): NetworkResult<R> = when (this) {
is Success -> transform(data)
else -> this as NetworkResult<R>
}
// 便捷访问
fun getOrNull(): T? = (this as? Success)?.data
fun getOrThrow(): T = when (this) {
is Success -> data
is Error -> throw exception
Loading -> throw IllegalStateException("Data still loading")
}
// 便捷组合
fun fold(
onSuccess: (T) -> Unit,
onError: (Throwable, Int?) -> Unit,
onLoading: () -> Unit = {}
) {
when (this) {
is Success -> onSuccess(data)
is Error -> onError(exception, code)
Loading -> onLoading()
}
}
}10. 常见错误与陷阱
10.1 忘记处理所有情况
kotlin
sealed class PaymentMethod {
object CreditCard : PaymentMethod()
object PayPal : PaymentMethod()
object Crypto : PaymentMethod()
}
// ❌ 错误:编译器会报错,但有时会被忽略
fun processPayment(method: PaymentMethod) {
when (method) {
is PaymentMethod.CreditCard -> processCard()
is PaymentMethod.PayPal -> processPayPal()
// 忘记处理 Crypto
}
}
// ✅ 正确:确保处理所有情况
fun processPayment(method: PaymentMethod) {
when (method) {
is PaymentMethod.CreditCard -> processCard()
is PaymentMethod.PayPal -> processPayPal()
is PaymentMethod.Crypto -> processCrypto()
}
}10.2 在错误位置定义子类
kotlin
// ❌ 错误:子类在不同文件
// File: Base.kt
sealed class Animal {
data class Dog(val name: String) : Animal()
}
// File: Cat.kt
data class Cat(val name: String) : Animal() // 编译错误!
// ✅ 正确:在同一文件
// File: Animals.kt
sealed class Animal {
data class Dog(val name: String) : Animal()
data class Cat(val name: String) : Animal()
}10.3 过度使用密封类
kotlin
// ❌ 错误:简单的标志位不需要密封类
sealed class BooleanState {
object True : BooleanState()
object False : BooleanState()
}
// ✅ 正确:使用 Boolean
val isComplete = true
// ❌ 错误:可以用 Enum 的场景
sealed class WeekDayState {
object Monday : WeekDayState()
object Tuesday : WeekDayState()
object Wednesday : WeekDayState()
object Thursday : WeekDayState()
object Friday : WeekDayState()
object Saturday : WeekDayState()
object Sunday : WeekDayState()
}
// ✅ 正确:使用 Enum
enum class WeekDay {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}10.4 性能问题
kotlin
// ❌ 错误:频繁创建临时对象
sealed class Calculation {
data class Addition(val a: Int, val b: Int) : Calculation()
data class Subtraction(val a: Int, val b: Int) : Calculation()
}
fun processCalculations(calculations: List<Calculation>) {
for (calc in calculations) {
val temp = when (calc) {
is Calculation.Addition -> calc.a + calc.b
is Calculation.Subtraction -> calc.a - calc.b
}
// 每次循环都创建新的临时对象
}
}
// ✅ 正确:减少不必要的对象创建
fun processCalculations(calculations: List<Calculation>) {
val results = calculations.map { calc ->
when (calc) {
is Calculation.Addition -> calc.a + calc.b
is Calculation.Subtraction -> calc.a - calc.b
}
}
}11. 性能优化
11.1 对象复用
kotlin
// 对于无状态的子类,使用 object 实现单例
sealed class Operator {
object Plus : Operator()
object Minus : Operator()
object Multiply : Operator()
object Divide : Operator()
// 这些 object 是单例,不会重复创建
}
// 性能测试
fun benchmark() {
val start = System.nanoTime()
repeat(1000000) {
val op = Operator.Plus // 直接引用,无对象创建开销
}
val end = System.nanoTime()
println("Time: ${(end - start) / 1_000_000} ms")
}11.2 减少 when 分支
kotlin
// ❌ 低效:过多的分支
sealed class Color {
object Red : Color()
object Green : Color()
object Blue : Color()
object Yellow : Color()
object Purple : Color()
object Orange : Color()
object Brown : Color()
object Black : Color()
object White : Color()
object Gray : Color()
// ... 更多颜色
}
fun getColorCategory(color: Color): String {
return when (color) {
Color.Red, Color.Yellow, Color.Orange -> "暖色"
Color.Blue, Color.Green, Color.Purple -> "冷色"
Color.White, Color.Black, Color.Gray -> "中性色"
Color.Brown -> "大地色"
}
}
// ✅ 高效:使用密封类分组
sealed class Color {
sealed class Warm : Color() {
object Red : Warm()
object Yellow : Warm()
object Orange : Warm()
}
sealed class Cool : Color() {
object Blue : Cool()
object Green : Cool()
object Purple : Cool()
}
sealed class Neutral : Color() {
object White : Neutral()
object Black : Neutral()
object Gray : Neutral()
}
object Brown : Color()
}
fun getColorCategory(color: Color): String = when (color) {
is Color.Warm -> "暖色"
is Color.Cool -> "冷色"
is Color.Neutral -> "中性色"
Color.Brown -> "大地色"
}11.3 避免不必要的装箱
kotlin
// ❌ 低效:使用对象包装基本类型
sealed class NumberResult {
data class IntValue(val value: Int) : NumberResult()
data class LongValue(val value: Long) : NumberResult()
data class DoubleValue(val value: Double) : NumberResult()
}
// ✅ 高效:使用泛型
sealed class NumberResult<T : Number> {
abstract val value: T
class IntValue(override val value: Int) : NumberResult<Int>()
class LongValue(override val value: Long) : NumberResult<Long>()
class DoubleValue(override val value: Double) : NumberResult<Double>()
}12. 面试考点
12.1 基础考点
Q1: 什么是密封类?它与抽象类有什么区别?
A:
- 密封类是受限的类层次结构,所有子类必须在同一文件中定义
- 抽象类的继承是开放的,可以从任何地方继承
- 密封类默认是抽象的,但不能显式声明为
abstract或open - 密封类主要用于 when 表达式的 exhaustive 检查
Q2: 密封类的所有子类必须在同一文件中,包括内部类吗?
A: 是的,密封类的子类必须在同一文件中,但可以在:
- 同一文件的顶层
- 同一文件的对象或类内部(作为内部类)
kotlin
sealed class Shape {
// 内部类
class Circle(val radius: Double) : Shape()
// 嵌套在对象中
object Square {
class Instance(val size: Double) : Shape()
}
}
// 顶层类
data class Triangle(val a: Double, val b: Double, val c: Double) : Shape()Q3: 密封类可以有构造器参数吗?
A: 可以,密封类本身和它的所有子类都可以有构造器参数:
kotlin
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val message: String, val code: Int) : Result<Nothing>()
}12.2 进阶考点
Q4: 密封类和枚举的区别是什么?如何选择?
A:
| 特性 | 枚举 | 密封类 |
|---|---|---|
| 实例 | 固定单例 | 可创建多个实例 |
| 数据 | 所有实例相同 | 每个子类不同 |
| 继承 | 不支持 | 支持多层 |
| when exhaustive | ✅ | ✅ |
| 性能 | 更优 | 略低 |
选择:
- 固定状态集、不需要数据 → Enum
- 状态需要携带数据 → Sealed Class
- 需要继承层次 → Sealed Class
Q5: 如何在使用密封类时优化性能?
A:
- 使用 object 代替 data class(无参数时)
- 减少 when 分支数量(通过嵌套分组)
- 避免在循环中频繁创建对象
- 使用泛型上界减少类型转换
kotlin
// 性能优化示例
sealed class Operator {
// object 是单例
object Plus : Operator()
object Minus : Operator()
// 减少分支
sealed class Arithmetic : Operator() {
object Add : Arithmetic()
object Subtract : Arithmetic()
}
sealed class Comparison : Operator() {
object GreaterThan : Comparison()
object LessThan : Comparison()
}
}Q6: 密封类在多模块项目中有什么限制?
A:
- 限制: 所有子类必须在同一文件中
- 解决方案:
- 将密封类和子类放在公共模块
- 使用接口代替密封类(牺牲 exhaustive 检查)
- 使用 sealed interface(Kotlin 1.9+ 多平台)
12.3 高级考点
Q7: 实现一个完整的状态管理系统,使用密封类
A:
kotlin
sealed class ViewState<T> {
data class Idle<T>() : ViewState<T>()
data class Loading<T>(val progress: Int = 0, val currentStep: String = "") : ViewState<T>()
data class Success<T>(val data: T, val hasMore: Boolean = false) : ViewState<T>()
data class Error<T>(val error: String, val canRetry: Boolean = true) : ViewState<T>()
data class Empty<T>(val hint: String = "") : ViewState<T>()
// 组合操作
fun <R> map(transform: (T) -> R): ViewState<R> = when (this) {
is Success -> ViewState.Success(transform(data), hasMore)
is Loading -> ViewState.Loading(progress, currentStep) as ViewState<R>
is Error -> ViewState.Error(error, canRetry) as ViewState<R>
is Empty -> ViewState.Empty(hint) as ViewState<R>
is Idle -> ViewState.Idle() as ViewState<R>
}
// 状态转换
fun isLoading(): Boolean = this is Loading
fun isSuccess(): Boolean = this is Success
fun isError(): Boolean = this is Error
}
// ViewModel 中的使用
class DataViewModel : ViewModel() {
private val _state = MutableStateFlow<ViewState<List<Item>>>(ViewState.Idle())
val state: StateFlow<ViewState<List<Item>>> = _state.asStateFlow()
fun loadItems(page: Int = 0) {
_state.update { ViewState.Loading(0, "加载中...") }
viewModelScope.launch {
try {
val items = repository.getItems(page)
_state.value = ViewState.Success(items)
} catch (e: Exception) {
_state.value = ViewState.Error(e.message ?: "加载失败")
}
}
}
}
// UI 处理
fun ViewBinding.bind(state: ViewState<List<Item>>) {
when (state) {
is ViewState.Idle -> showInitial()
is ViewState.Loading -> showLoading(state.progress, state.currentStep)
is ViewState.Success -> {
showItems(state.data)
if (state.hasMore) showLoadMore()
}
is ViewState.Error -> {
showError(state.error)
if (state.canRetry) showRetryButton()
}
is ViewState.Empty -> showEmpty(state.hint)
}
}Q8: 设计一个支持撤销/重做的命令模式
A:
kotlin
sealed class Command {
data class AddItem(val item: Item) : Command()
data class RemoveItem(val itemId: String) : Command()
data class UpdateItem(val itemId: String, val updates: Map<String, Any>) : Command()
// 撤销操作
abstract fun undo(): Command
override fun undo(): Command = when (this) {
is AddItem -> RemoveItem(item.id)
is RemoveItem -> AddItem(Item(id = itemId))
is UpdateItem -> UpdateItem(itemId, emptyMap())
}
}
class CommandHistory {
private val undoStack = Stack<Command>()
private val redoStack = Stack<Command>()
fun execute(command: Command) {
// 执行命令
// ...
undoStack.push(command)
redoStack.clear()
}
fun undo(): Command? {
if (undoStack.isEmpty()) return null
val command = undoStack.pop()
command.undo().let {
// 执行撤销命令
redoStack.push(it)
}
return command
}
fun redo(): Command? {
if (redoStack.isEmpty()) return null
val command = redoStack.pop()
// 执行重做命令
undoStack.push(command)
return command
}
}Q9: 如何在协程中使用密封类处理异步状态?
A:
kotlin
sealed class AsyncResult<out T> {
data class Pending<out T>() : AsyncResult<T>()
data class Success<out T>(val data: T) : AsyncResult<T>()
data class Failure<out T>(val exception: Throwable) : AsyncResult<T>()
// 转换为 Flow
fun asFlow(): Flow<AsyncResult<T>> = flow {
emit(this@asFlow)
}
// 转换为 StateFlow
fun asStateFlow(): StateFlow<AsyncResult<T>> = MutableStateFlow(this@asStateFlow)
}
// 使用示例
class DataRepository {
suspend fun fetchData(): AsyncResult<Data> {
return try {
AsyncResult.Success(api.getData())
} catch (e: Exception) {
AsyncResult.Failure(e)
}
}
}
class DataViewModel : ViewModel() {
private val _result = MutableStateFlow<AsyncResult<Data>>(AsyncResult.Pending())
val result: StateFlow<AsyncResult<Data>> = _result
init {
viewModelScope.launch {
_result.value = AsyncResult.Pending()
val result = repository.fetchData()
_result.value = result
}
}
}
// UI 观察
lifecycleScope.launch {
viewModel.result.collect { result ->
when (result) {
is AsyncResult.Pending -> showLoading()
is AsyncResult.Success -> showData(result.data)
is AsyncResult.Failure -> showError(result.exception)
}
}
}Q10: 密封类在响应式编程中的应用
A:
kotlin
sealed class DataChange {
data class Inserted<T>(val item: T) : DataChange<T>()
data class Updated<T>(val oldItem: T, val newItem: T) : DataChange<T>()
data class Removed<T>(val item: T) : DataChange<T>()
object Cleared : DataChange<Nothing>()
}
class DataStore<T> {
private val _changes = MutableSharedFlow<DataChange<T>>()
val changes: SharedFlow<DataChange<T>> = _changes
suspend fun insert(item: T) {
_changes.emit(DataChange.Inserted(item))
}
suspend fun update(oldItem: T, newItem: T) {
_changes.emit(DataChange.Updated(oldItem, newItem))
}
suspend fun remove(item: T) {
_changes.emit(DataChange.Removed(item))
}
}
// UI 处理
lifecycleScope.launch {
dataStore.changes.collect { change ->
when (change) {
is DataChange.Inserted -> adapter.insert(change.item)
is DataChange.Updated -> adapter.update(change.oldItem, change.newItem)
is DataChange.Removed -> adapter.remove(change.item)
DataChange.Cleared -> adapter.clear()
}
}
}12.4 实战题目
题目 1: 设计一个完整的用户认证状态系统
要求:
- 包含未登录、登录中、已登录、登录失败等状态
- 支持 Token 刷新
- 支持 Session 过期处理
kotlin
sealed class AuthenticationState {
// 未认证
object Unauthenticated : AuthenticationState()
// 认证中
sealed class Authenticating : AuthenticationState() {
object SigningIn : Authenticating()
object Registering : Authenticating()
data class RefreshingToken(val lastToken: String) : Authenticating()
}
// 已认证
data class Authenticated(
val token: String,
val refreshToken: String,
val expiresAt: Instant,
val user: UserInfo
) : AuthenticationState()
// 认证失败
sealed class AuthenticationFailed : AuthenticationState() {
data class InvalidCredentials(val reason: String) : AuthenticationFailed()
object SessionExpired : AuthenticationFailed()
data class ServerError(val code: Int, val message: String) : AuthenticationFailed()
object NetworkError : AuthenticationFailed()
}
// Token 即将过期
data class TokenExpiring(val remainingSeconds: Long) : AuthenticationState()
}
// 状态管理
class AuthenticationManager {
private val _state = MutableStateFlow<AuthenticationState>(AuthenticationState.Unauthenticated)
val state: StateFlow<AuthenticationState> = _state
suspend fun login(username: String, password: String): Result<Unit> {
_state.value = AuthenticationState.Authenticating.SigningIn
return try {
val tokens = api.login(username, password)
val user = api.getCurrentUser()
_state.value = AuthenticationState.Authenticated(
token = tokens.accessToken,
refreshToken = tokens.refreshToken,
expiresAt = Instant.now().plusSeconds(3600),
user = user
)
Result.success(Unit)
} catch (e: AuthException) {
_state.value = AuthenticationState.AuthenticationFailed.InvalidCredentials(e.message)
Result.failure(e)
} catch (e: NetworkException) {
_state.value = AuthenticationState.AuthenticationFailed.NetworkError
Result.failure(e)
}
}
suspend fun refreshToken(): Result<Unit> {
if (_state.value !is AuthenticationState.Authenticated) return Result.failure(IllegalStateException())
val current = _state.value as AuthenticationState.Authenticated
_state.value = AuthenticationState.Authenticating.RefreshingToken(current.token)
return try {
val tokens = api.refreshToken(current.refreshToken)
_state.value = AuthenticationState.Authenticated(
token = tokens.accessToken,
refreshToken = tokens.refreshToken,
expiresAt = Instant.now().plusSeconds(3600),
user = current.user
)
Result.success(Unit)
} catch (e: Exception) {
_state.value = AuthenticationState.AuthenticationFailed.SessionExpired
Result.failure(e)
}
}
fun logout() {
_state.value = AuthenticationState.Unauthenticated
}
// 监听 Token 过期
init {
viewModelScope.launch {
while (true) {
delay(60_000) // 每分钟检查
val state = _state.value
if (state is AuthenticationState.Authenticated) {
val remaining = state.expiresAt minus Instant.now()
if (remaining.toMillis() < 5 * 60 * 1000) { // 5 分钟内过期
_state.value = AuthenticationState.TokenExpiring(remaining.toSeconds())
refreshToken()
}
}
}
}
}
}题目 2: 实现一个支持错误恢复的网络请求状态
要求:
- 支持重试机制
- 区分可恢复和不可恢复错误
- 记录重试历史
kotlin
sealed class NetworkRequestState<T> {
data class Idle<T>() : NetworkRequestState<T>()
data class InProgress<T>(
val progress: Float = 0f,
val currentStep: String = "",
val attempt: Int = 1
) : NetworkRequestState<T>()
data class Success<T>(
val data: T,
val cached: Boolean = false,
val timestamp: Long = System.currentTimeMillis()
) : NetworkRequestState<T>()
sealed class Error<T> : NetworkRequestState<T>() {
// 可恢复错误
sealed class Retryable : Error<Nothing>() {
object Timeout : Retryable()
object NetworkUnavailable : Retryable()
object ServerUnavailable : Retryable()
}
// 不可恢复错误
sealed class NonRetryable : Error<Nothing>() {
data class NotFound(val resourceId: String) : NonRetryable()
data class Unauthorized(val reason: String) : NonRetryable()
data class InvalidRequest(val field: String, val message: String) : NonRetryable()
object Forbidden : NonRetryable()
}
data class RetryExhausted<T>(
val lastError: Error<T>,
val maxRetries: Int,
val retryHistory: List<RetryAttempt>
) : Error<T>()
}
}
data class RetryAttempt(
val attempt: Int,
val timestamp: Long,
val error: String,
val delay: Long
)
class NetworkRequestManager {
private val _state = MutableStateFlow<NetworkRequestState<Data>>(NetworkRequestState.Idle())
val state: StateFlow<NetworkRequestState<Data>> = _state
private val maxRetries = 3
private val retryDelays = listOf(1000L, 2000L, 4000L) // 指数退避
suspend fun fetchData(endpoint: String): NetworkRequestState<Data> {
_state.value = NetworkRequestState.InProgress(0f, "初始化", 1)
var retryHistory = emptyList<RetryAttempt>()
for (attempt in 1..maxRetries) {
_state.value = NetworkRequestState.InProgress(
progress = (attempt * 100 / maxRetries).toFloat(),
currentStep = "第 $attempt 次尝试",
attempt = attempt
)
try {
val response = api.fetch(endpoint)
return NetworkRequestState.Success(response.data)
} catch (e: RetryableException) {
retryHistory = retryHistory + RetryAttempt(
attempt = attempt,
timestamp = System.currentTimeMillis(),
error = e.message,
delay = retryDelays[attempt - 1]
)
if (attempt < maxRetries) {
delay(retryDelays[attempt - 1])
continue
}
}
}
return NetworkRequestState.Error.RetryExhausted(
lastError = NetworkRequestState.Error.Retryable.Timeout,
maxRetries = maxRetries,
retryHistory = retryHistory
)
}
}总结
密封类是 Kotlin 中一个强大的特性,它通过限制类层次结构,提供了编译期的类型安全性。在 Android 开发中,密封类广泛应用于:
- UI 状态管理 - 替代传统的 enum 和多个 boolean 标志
- 网络请求封装 - 优雅地处理 Success/Error/Loading 状态
- 结果封装 - 替代传统的 Result 类和异常处理
- 命令模式 - 实现可组合、可撤销的命令
- 状态机 - 建模复杂的业务状态流转
掌握密封类的使用,不仅能写出更安全的代码,还能在面试中展现对 Kotlin 特性的深刻理解。