位置:首页 > Kotlin > Kotlin中如何优化臃肿的when表达式写法

Kotlin中如何优化臃肿的when表达式写法

时间:2026-08-21  |  作者:白桃企划师  |  阅读:0

你是否管理过那些根据内部状态发生剧烈行为变化的复杂对象?

如果是,那你很可能已经遇到过一个常见问题:在类的每个方法里,都塞满了冗长的 when 语句。

它看起来大概是这样的:

fun handleAction() {
    when (state) {
        State.A -> { /* A 的逻辑 */ }
        State.B -> { /* B 的逻辑 */ }
        State.C -> { /* C 的逻辑 */ }
        // ... 还在不断增长!
    }
}

每次你添加一个新状态,比如 State.D,都要逐个追踪并更新每一个函数,再补上新的分支。

这会迅速演变成维护噩梦。它直接违反了开闭原则(OCP),也是明显的代码异味。

你的逻辑本该按状态分组,却因为动作而散落在各处。

为什么要换一种做法

问题的根源不在于 when 本身,而在于状态逻辑被分散到了多个动作方法里。

那么解决方案是什么?答案是状态模式

而且借助现代 Kotlin,我们可以用很少的样板代码,把它实现得足够优雅。

一个简单的自动售货机

假设我们正在构建一个简单的 VendingMachine。它有几个关键状态和动作:

  • 状态IDLE(空闲)、HAS_MONEY(已投币)、OUT_OF_STOCK(缺货)
  • 动作insertMoney(投币)、selectProduct(选择商品)、requestRefund(请求退款)

下面是那种存在问题的、基于状态检查的代码:

class VendingMachine(private var balance: Int = 0) {
    private var state: State = State.IDLE
    fun insertMoney(amount: Int) {
        when (state) {
            State.IDLE -> {
                balance += amount
                state = State.HAS_MONEY
                println("已投币。余额:$balance")
            }
            State.HAS_MONEY -> {
                println("已投币,无需重复投币。余额:$balance")
            }
            State.OUT_OF_STOCK -> {
                println("机器缺货。")
            }
        }
    }
    fun selectProduct(code: String) {
        when (state) {
            State.IDLE -> {
                println("请先投币。")
            }
            State.HAS_MONEY -> {
                if (/* 商品可用 */) {
                    println("商品已售出。")
                    state = State.IDLE
                } else {
                    println("商品不可用。")
                }
            }
            State.OUT_OF_STOCK -> {
                println("机器缺货。")
            }
        }
    }
    fun requestRefund() {
        when (state) {
            State.IDLE -> {
                println("没有可退的款项。")
            }
            State.HAS_MONEY -> {
                println("已退款:$balance")
                balance = 0
                state = State.IDLE
            }
            State.OUT_OF_STOCK -> {
                println("没有可退的款项。")
            }
        }
    }
}

如果我们新增一个 MAINTENANCE(维护)状态,就会破坏开闭原则。

因为我们必须修改 insertMoneyselectProduct,以及其他所有动作方法。

经典的状态模式

状态模式通过将“状态”本身变成一个持有行为的对象,来解决这个问题。

核心思路是:按状态组织行为,而不是按动作组织行为。

1. 定义接口

先定义一个通用接口 MachineState

所有状态对象都要实现它。这个接口涵盖了上下文 VendingMachineContext 能执行的全部动作。

interface MachineState {
    fun insertMoney(amount: Int, context: VendingMachineContext)
    fun selectProduct(code: String, context: VendingMachineContext)
    fun requestRefund(context: VendingMachineContext)
}

2. 具体实现

接着,为每个状态分别实现对应行为。

关键点在于:单个状态的所有逻辑,现在都集中在一个类或对象中。

object IdleState : MachineState {
    override fun insertMoney(amount: Int, context: VendingMachineContext) {
        context.balance += amount
        context.state = HasMoneyState
        println("已投币。余额:${context.balance}")
    }
    override fun selectProduct(code: String, context: VendingMachineContext) {
        println("请先投币。")
    }
    override fun requestRefund(context: VendingMachineContext) {
        println("没有可退的款项。")
    }
}

object HasMoneyState : MachineState {
    override fun insertMoney(amount: Int, context: VendingMachineContext) {
        context.balance += amount
        println("已添加金额。余额:${context.balance}")
    }
    override fun selectProduct(code: String, context: VendingMachineContext) {
        if (context.balance >= 2) {
            context.balance -= 2
            println("商品已售出。")
            context.state = IdleState
        } else {
            println("商品不可用。")
        }
    }
    override fun requestRefund(context: VendingMachineContext) {
        println("已退款:${context.balance}")
        context.balance = 0
        context.state = IdleState
    }
}

object OutOfStockState : MachineState {
    override fun insertMoney(amount: Int, context: VendingMachineContext) {
        println("机器缺货。")
    }
    override fun selectProduct(code: String, context: VendingMachineContext) {
        println("机器缺货。")
    }
    override fun requestRefund(context: VendingMachineContext) {
        println("没有可退的款项。")
    }
}

3. 上下文

VendingMachineContext 现在会简洁很多。

它只需要把动作委托给当前状态对象即可。

class VendingMachineContext(var balance: Int = 0) {
    var state: MachineState = IdleState
    fun insertMoney(amount: Int) {
        state.insertMoney(amount, this)
    }
    fun selectProduct(code: String) {
        state.selectProduct(code, this)
    }
    fun requestRefund() {
        state.requestRefund(this)
    }
    // 辅助函数,供状态对象切换机器状态
    fun transitionTo(newState: MachineState) {
        this.state = newState
    }
}

这样一来,如果新增一个状态,只需创建一个新的类。

你无需修改 VendingMachineContext,也不用改其他状态类。开闭原则得到了恢复。

增强的 Kotlin 风格状态模式

经典模式很可靠,但 Kotlin 还能让它更强大、更优雅。

做法是使用密封接口,来强制实现类型安全的命令。

1. 定义所有输入

我们先用密封接口,建模机器可以接收的所有输入:

sealed interface VendingInput {
    data class InsertMoney(val amount: Int) : VendingInput
    data class SelectProduct(val code: String) : VendingInput
    object RequestRefund : VendingInput
}

2. 定义状态和上下文

此时,状态接口只接受一个 VendingInput

而上下文只暴露一个统一的 process 方法。


sealed interface VendingState {
    fun handle(input: VendingInput, context: VendingMachineContext)
}

class VendingMachineContext(var balance: Int = 0) {
    var state: VendingState = IdleState
    fun process(input: VendingInput) {
        state.handle(input, this)
    }
    fun transitionTo(newState: VendingState) {
        this.state = newState
    }
}

3. 行为实现

这里每个状态对象只保留一个针对 inputwhen 语句。

由于 VendingInput 是密封的,Kotlin 会强制我们处理所有可能输入。

这让代码具备了穷举性安全性


object IdleState : VendingState {
    override fun handle(input: VendingInput, context: VendingMachineContext) {
        when (input) {
            is VendingInput.InsertMoney -> {
                context.balance += input.amount
                context.transitionTo(HasMoneyState)
                println("已投币。余额:${context.balance}")
            }
            is VendingInput.SelectProduct -> {
                println("请先投币。")
            }
            VendingInput.RequestRefund -> {
                println("没有可退的款项。")
            }
        }
    }
}

object HasMoneyState : VendingState {
    override fun handle(input: VendingInput, context: VendingMachineContext) {
        when (input) {
            is VendingInput.InsertMoney -> {
                context.balance += input.amount
                println("已添加金额。余额:${context.balance}")
            }
            is VendingInput.SelectProduct -> {
                if (context.balance >= 2) {
                    context.balance -= 2
                    println("商品已售出。")
                    context.transitionTo(IdleState)
                } else {
                    println("商品不可用。")
                }
            }
            VendingInput.RequestRefund -> {
                println("已退款:${context.balance}")
                context.balance = 0
                context.transitionTo(IdleState)
            }
        }
    }
}

object OutOfStockState : VendingState {
    override fun handle(input: VendingInput, context: VendingMachineContext) {
        when (input) {
            is VendingInput.InsertMoney -> {
                println("机器缺货。")
            }
            is VendingInput.SelectProduct -> {
                println("机器缺货。")
            }
            VendingInput.RequestRefund -> {
                println("没有可退的款项。")
            }
        }
    }
}

纯函数式状态转换

对于真正健壮的系统,比如 MVI,你可能希望尽量避免可变性。

这时可以让状态函数直接返回下一个状态,从而让系统具备更高的确定性。

1. 定义转换

// 1. 定义所有输入(保持不变,密封接口非常适合函数式)
sealed interface VendingInput {
    data class InsertMoney(val amount: Int) : VendingInput
    data class SelectProduct(val code: String) : VendingInput
    object RequestRefund : VendingInput
}

// 2. 定义状态:将数据(balance)与状态逻辑结合
// 使用不可变属性 (val),确保状态一旦创建不可修改
sealed class VendingState(val balance: Int) {
    
    // 核心转变:函数签名返回 (新状态, 产生的结果文本)
    abstract fun handle(input: VendingInput): Pair

    // --- 各个状态的具体实现 ---

    data class Idle(private val b: Int = 0) : VendingState(b) {
        override fun handle(input: VendingInput) = when (input) {
            is VendingInput.InsertMoney -> 
                HasMoney(balance + input.amount) to "已投币。余额:${balance + input.amount}"
            is VendingInput.SelectProduct -> 
                this to "请先投币。"
            VendingInput.RequestRefund -> 
                this to "没有可退的款项。"
        }
    }

    data class HasMoney(private val b: Int) : VendingState(b) {
        override fun handle(input: VendingInput) = when (input) {
            is VendingInput.InsertMoney -> 
                copy(b = balance + input.amount) to "已添加金额。余额:${balance + input.amount}"
            is VendingInput.SelectProduct -> 
                if (balance >= 2) Idle(balance - 2) to "商品已售出:${input.code}。"
                else this to "余额不足。"
            VendingInput.RequestRefund -> 
                Idle(0) to "已退款:$balance"
        }
    }

    object OutOfStock : VendingState(0) {
        override fun handle(input: VendingInput) = this to "机器缺货。"
    }
}

2. 使用举例

fun main() {
    val s0 = VendingState.Idle()

    // 所有的处理结果都作为新值返回,原始 s0 保持不变
    val (s1, msg1) = s0.handle(VendingInput.InsertMoney(5))
    println(msg1) // 已投币。余额:5

    val (s2, msg2) = s1.handle(VendingInput.SelectProduct("可乐"))
    println(msg2) // 商品已售出:可乐。

    // s2 现在是 Idle 状态,余额已扣除
    println("最终状态: ${s2::class.simpleName}, 余额: ${s2.balance}")
}

这种方式虽然更复杂,但在响应式架构中很常见。

因为在这类系统里,状态变化必须被精确跟踪。

它的核心其实很简单:

  • 函数返回新的状态结果
  • 状态本身保持不可变
  • 同一个状态的数据发生变化时,会生成一个新的状态

总结

通过应用状态模式,我们成功地将分散、脆弱的条件逻辑,替换为干净、内聚的状态对象。

这让代码更易于维护、测试和扩展。

如果你的 Kotlin 代码库正遭受“臃肿的 when 语句”之苦,不妨试试状态模式。

你的团队会感谢你的。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多