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(维护)状态,就会破坏开闭原则。
因为我们必须修改 insertMoney、selectProduct,以及其他所有动作方法。
经典的状态模式
状态模式通过将“状态”本身变成一个持有行为的对象,来解决这个问题。
核心思路是:按状态组织行为,而不是按动作组织行为。
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. 行为实现
这里每个状态对象只保留一个针对 input 的 when 语句。
由于 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 语句”之苦,不妨试试状态模式。
你的团队会感谢你的。
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- 迅捷路由器怎么调信号最强,设置时要注意什么?
- 时间:2026-08-27
-
- vivo浏览器怎么卸不掉?原因和解决方法在这里
- 时间:2026-08-27
-
- OPPO R11s黑屏了,怎么强制恢复出厂设置?
- 时间:2026-08-27
-
- 飞利浦显示器包装盒有生产日期和保修期吗?怎么看?
- 时间:2026-08-27
-
- 联想新平板开机必须联网吗?怎么做?
- 时间:2026-08-27
-
- 平板横竖屏切换设置与问题解决
- 时间:2026-08-27
-
- 移动电源容量怎么测?要准备哪些工具?
- 时间:2026-08-27
-
- 荣耀90 Pro防水吗?防水级别多少?怎么用才安全
- 时间:2026-08-27
精选合集
更多大家都在玩
大家都在看
更多-
- 2026年9月17日小鸡庄园答案
- 时间:2026-09-16
-
- 蚂蚁庄园今日答案2026年9月17日
- 时间:2026-09-16
-
- 蚂蚁庄园小课堂今日最新答案2026年9月17日
- 时间:2026-09-16
-
- 蚂蚁庄园小鸡答题今日答案2026年9月17日
- 时间:2026-09-16
-
- 褪黑素主要由人体哪个器官分泌 蚂蚁庄园今日答案9.17
- 时间:2026-09-16
-
- 蚂蚁庄园今天答题答案2026年9月17日
- 时间:2026-09-16
-
- 蚂蚁庄园答题今日答案2026年9月17日
- 时间:2026-09-16
-
- 研学旅游指导师的核心服务对象是 蚂蚁新村今日答案2026.9.16
- 时间:2026-09-16