Appearance
🌟 Kotlin 密封类:像管理快递驿站一样管理代码状态
想象你经营一家快递驿站 📦,每天要处理三类包裹:
- 待揽收(需要称重+寄件人)
- 运输中(需要运单号+出发时间)
- 已签收(需要签收人+时间)
如果用传统方式建模,就像把所有包裹扔进同一个大筐子 👇:
kotlin
// 😵 混乱的"万能包裹类"
data class Package(
val status: String, // 状态字段
val weight: Double? = null, // 待揽收专用(可能为null)
val trackingNum: String? = null, // 运输中专用(可能为null)
val receiver: String? = null // 已签收专用(可能为null)
)❌ 传统方式的三大痛点:
空值炸弹 💣
80%的字段在错误状态下是null,随时可能爆炸kotlinval p = Package("已签收") println(p.weight) // 返回null -> 可能引发崩溃类型陷阱 🕳️
编译器不会阻止你访问错误字段kotlinfun process(p: Package) { if(p.status == "已签收") { println(p.trackingNum.length) // 运输中才有的字段! } }代码臃肿 🐘
每个操作都要写一堆if-else判空:kotlinfun getInfo(p: Package): String { return when(p.status) { "待揽收" -> "重量:${p.weight ?: "未知"}" // 需要判空 "运输中" -> "单号:${p.trackingNum ?: "未知"}" else -> "状态错误" } }
🚀 密封类解决方案:智能包裹分拣机
密封类就像给驿站安装智能分拣柜,每个状态都有专属格子:
kotlin
// ✨ 创建密封类作为基类(快递柜本体)
sealed class PackageState(val id: String)
// 🧩 每个状态独立子类(专属格子)
data class AwaitingPickup(
val id: String,
val weight: Double, // 专属字段(非空!)
val sender: String // 专属字段(非空!)
) : PackageState(id) // 继承基类
data class InTransit(
val id: String,
val trackingNum: String, // 专属字段(非空!)
val departureTime: String // 专属字段(非空!)
) : PackageState(id)💡 核心原理:密封类要求所有子类必须在同一文件定义,形成封闭家族
🔍 三大超能力解析
1. 家族保护盾 🛡️ - "禁止外部认亲"
kotlin
// 文件:Payment.kt
sealed class PaymentResult // 密封类
// ✅ 允许:同文件内继承
class Success(val receipt: String) : PaymentResult()
// ❌ 禁止:其他文件试图继承
// 文件:Hacker.kt
// class Fraud() : PaymentResult() // 编译错误!密封类只能被同文件继承2. 编译器读心术 🔮 - "自动识别状态"
kotlin
fun printStatus(state: PackageState) = when(state) {
is AwaitingPickup ->
"待取件包裹,重量:${state.weight}kg" // 直接访问weight
is InTransit ->
"运输中,单号:${state.trackingNum}" // 直接访问trackingNum
// ✨ 无需else分支!编译器确保覆盖所有情况
}3. 智能变身术 🦸 - "自动类型转换"
kotlin
val currentState = getPackageState() // 返回PackageState类型
if (currentState is InTransit) {
// 🎉 自动识别为InTransit类型!
trackPackage(currentState.trackingNum) // 安全访问专属字段
// 编译器知道currentState一定是InTransit
println(currentState.departureTime)
}🛠️ 实战三步曲:构建快递系统
步骤 1:完整状态建模(添加业务逻辑)
kotlin
sealed class PackageState(val id: String) {
// 每个状态实现专属逻辑
abstract fun calculateShippingFee(): Double
}
data class International(
val id: String,
val country: String,
val customsTax: Double
) : PackageState(id) {
// 国际件专属计算逻辑
override fun calculateShippingFee() = 200 + customsTax
}
data class LocalDelivery(
val id: String,
val distance: Int
) : PackageState(id) {
// 同城件专属计算逻辑
override fun calculateShippingFee() = distance * 5
}步骤 2:状态处理器(安全访问字段)
kotlin
fun handlePackage(state: PackageState) = when(state) {
is International -> {
prepareCustomsDocuments(state.country) // ✅ 安全访问country
state.calculateShippingFee() // ✅ 调用专属方法
}
is LocalDelivery -> {
assignBikeCourier(state.distance) // ✅ 安全访问distance
state.calculateShippingFee()
}
// 编译器会检查是否覆盖所有子类!
}步骤 3:API 接口应用(杜绝无效状态)
kotlin
@PostMapping("/create")
fun createPackage(@RequestBody request: CreateRequest) {
val state = when(request.type) {
"INTERNATIONAL" -> International(
request.id,
request.country, // 必有值
request.tax // 必有值
)
"LOCAL" -> LocalDelivery(
request.id,
request.distance // 必有值
)
// 新增状态只需加分支,不会影响旧逻辑
}
repository.save(state)
}⚡ 高级技巧锦囊
状态机验证(禁止非法状态流转)
kotlin
// 定义合法转换规则
val allowedTransitions = mapOf(
AwaitingPickup::class to setOf(InTransit::class),
InTransit::class to setOf(Delivered::class)
)
fun changeState(old: PackageState, new: PackageState): Boolean {
return new::class in allowedTransitions[old::class]
?: false // 不在允许列表则拒绝
}协程实时状态推送
kotlin
suspend fun trackPackage(id: String) = flow {
emit(AwaitingPickup(id, 2.5, "小明"))
delay(3000)
emit(InTransit(id, "SF2024", "10:00")) // 状态更新
}.collect { state ->
when(state) { // 自动识别当前状态
is InTransit ->
showNotification("已发货!单号:${state.trackingNum}")
is Delivered ->
showNotification("已签收!签收人:${state.receiver}")
}
}🏆 最佳实践指南
领域驱动设计
子类对应真实业务概念:kotlin// 破损件状态 data class Damaged( val id: String, val damageDesc: String, // 损坏描述 val compensation: Double // 赔偿金额 ) : PackageState(id)工厂模式集成
统一创建入口:kotlinobject StateFactory { fun create(type: String, id: String) = when(type) { "DAMAGED" -> Damaged(id, "外包装破损", 50.0) "RETURNED" -> Returned(id, "尺码不符") // 扩展时只需添加新分支 } }不可变原则
使用data class自动生成工具方法:kotlindata class Returned( // 自动生成equals/hashCode/toString val id: String, val reason: String ) : PackageState(id)
💎 核心价值总结
| 场景 | 传统方式 | 密封类方案 |
|---|---|---|
| 状态关联数据 | 混杂空字段 | 精准匹配 |
| 类型安全 | 需手动检查 | 编译器自动保障 |
| 扩展性 | 修改影响全局 | 新增子类无影响 |
| 可读性 | 逻辑分散 | 状态集中管理 |
🌈 四大应用场景:
- 订单状态:待付款/已发货/已完成
- 游戏角色:待机/攻击/受伤
- UI 状态:加载中/成功/错误
- API 响应:成功/失败/超时
就像高效的快递分拣中心,密封类让每个状态在专属通道安全流转,从此告别NullPointerException噩梦!🚚✨