Skip to content

🌟 Kotlin 密封类:像管理快递驿站一样管理代码状态

想象你经营一家快递驿站 📦,每天要处理三类包裹:

  • 待揽收(需要称重+寄件人)
  • 运输中(需要运单号+出发时间)
  • 已签收(需要签收人+时间)

如果用传统方式建模,就像把所有包裹扔进同一个大筐子 👇:

kotlin
// 😵 混乱的"万能包裹类"
data class Package(
    val status: String,           // 状态字段
    val weight: Double? = null,     // 待揽收专用(可能为null)
    val trackingNum: String? = null, // 运输中专用(可能为null)
    val receiver: String? = null    // 已签收专用(可能为null)
)

❌ 传统方式的三大痛点:

  1. 空值炸弹 💣
    80%的字段在错误状态下是null,随时可能爆炸

    kotlin
    val p = Package("已签收")
    println(p.weight) // 返回null -> 可能引发崩溃
  2. 类型陷阱 🕳️
    编译器不会阻止你访问错误字段

    kotlin
    fun process(p: Package) {
        if(p.status == "已签收") {
            println(p.trackingNum.length) // 运输中才有的字段!
        }
    }
  3. 代码臃肿 🐘
    每个操作都要写一堆if-else判空:

    kotlin
    fun 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}")
    }
}

🏆 最佳实践指南

  1. 领域驱动设计
    子类对应真实业务概念:

    kotlin
    // 破损件状态
    data class Damaged(
        val id: String,
        val damageDesc: String,  // 损坏描述
        val compensation: Double // 赔偿金额
    ) : PackageState(id)
  2. 工厂模式集成
    统一创建入口:

    kotlin
    object StateFactory {
        fun create(type: String, id: String) = when(type) {
            "DAMAGED" -> Damaged(id, "外包装破损", 50.0)
            "RETURNED" -> Returned(id, "尺码不符")
            // 扩展时只需添加新分支
        }
    }
  3. 不可变原则
    使用data class自动生成工具方法:

    kotlin
    data class Returned( // 自动生成equals/hashCode/toString
        val id: String,
        val reason: String
    ) : PackageState(id)

💎 核心价值总结

场景传统方式密封类方案
状态关联数据混杂空字段精准匹配
类型安全需手动检查编译器自动保障
扩展性修改影响全局新增子类无影响
可读性逻辑分散状态集中管理

🌈 四大应用场景

  1. 订单状态:待付款/已发货/已完成
  2. 游戏角色:待机/攻击/受伤
  3. UI 状态:加载中/成功/错误
  4. API 响应:成功/失败/超时

就像高效的快递分拣中心,密封类让每个状态在专属通道安全流转,从此告别NullPointerException噩梦!🚚✨