返回

Kotlin `when` 替代方案:告别臃肿代码

Android

Kotlin 中 when 语句的替代方案

在 Kotlin 开发中,when 语句是一种强大的条件控制结构,能够清晰地处理多种情况。然而,随着应用复杂度的提升,when 语句也可能变得臃肿,难以维护。特别是在处理枚举类或密封类时,不断添加新的 when 分支会影响代码的可读性和可扩展性。

本文将探讨一些避免“无限 when”代码块的替代方案,旨在提供更优雅和可维护的 Kotlin 代码结构。

1. 多态与接口

when 语句基于对象的类型进行分发时,可以考虑使用多态和接口。通过定义一个包含公共方法的接口或抽象类,不同的事件类型可以实现各自的逻辑。这样,可以将原本集中在 when 语句中的代码分散到各个类中。

示例:

假设存在一个 Event 接口:

interface Event {
    fun handle()
}

class EventA : Event {
    override fun handle() {
        // 处理 EventA
        println("Handling Event A")
    }
}

class EventB : Event {
    override fun handle() {
        // 处理 EventB
        println("Handling Event B")
    }
}

fun processEvent(event: Event) {
    event.handle()
}

fun main() {
    val eventA = EventA()
    val eventB = EventB()

    processEvent(eventA)
    processEvent(eventB)
}

说明: 通过定义 Event 接口,避免了 when 语句。 processEvent 函数直接调用 event.handle(),实现了针对不同事件类型的多态处理。每增加一种新的事件类型,只需实现 Event 接口并实现 handle 方法,无需修改现有的代码。

步骤:

  1. 定义一个包含公共方法的接口。
  2. 创建实现了该接口的具体事件类。
  3. 在需要处理事件的地方,直接调用接口方法。

2. 函数式编程:高阶函数与函数类型

函数式编程的思想也可以应用于解决 when 语句膨胀的问题。 可以利用高阶函数和函数类型,将不同事件的处理逻辑封装成独立的函数,并使用 Map 数据结构将事件类型与相应的处理函数关联起来。

示例:

sealed class UserEvent {
    object A : UserEvent()
    object B : UserEvent()
    object C : UserEvent()
    object D : UserEvent()
}

val eventHandlers: Map<KClass<out UserEvent>, (UserEvent) -> Unit> = mapOf(
    UserEvent.A::class to { event ->
        // 处理 EventA
        println("Handling Event A")
    },
    UserEvent.B::class to { event ->
        // 处理 EventB
        println("Handling Event B")
    },
    UserEvent.C::class to { event ->
        // 处理 EventC
        println("Handling Event C")
    },
    UserEvent.D::class to { event ->
        // 处理 EventD
        println("Handling Event D")
    }
)

fun parseUserEvent(userEvent: UserEvent) {
    val handler = eventHandlers[userEvent::class]
    handler?.invoke(userEvent) ?: println("Unknown event type")
}

import kotlin.reflect.KClass
fun main() {
    parseUserEvent(UserEvent.A)
    parseUserEvent(UserEvent.C)
    parseUserEvent(object: UserEvent(){}) // will print 'Unknown event type'

}

说明:

  • eventHandlers 是一个 Map,其中键是 UserEvent 类型的类对象,值是接收 UserEvent 对象并执行相应处理的函数。
  • parseUserEvent 函数使用 userEvent::class 获取 UserEvent 对象的类对象,并在 eventHandlers 映射中查找相应的处理函数。
  • 如果找到处理函数,则使用 invoke 方法执行该函数,并将 UserEvent 对象作为参数传递给它。

步骤:

  1. 创建一个 Map,其中键为事件类型,值为处理该事件的函数。
  2. 在处理事件的函数中,使用 Map 查找相应的处理函数并执行。

安全建议: 增加一个默认的处理函数,处理所有未知的事件类型。 避免程序因找不到对应的处理器而崩溃。

3. Visitor 模式

Visitor 模式是一种行为型设计模式,它允许在不修改对象结构的前提下定义新的操作。这可以通过创建一个“访问者”接口来实现,该接口定义了对每个事件类型的访问方法。

示例:

interface UserEventVisitor {
    fun visit(event: UserEvent.A)
    fun visit(event: UserEvent.B)
    fun visit(event: UserEvent.C)
    fun visit(event: UserEvent.D)
}

sealed class UserEvent {
    object A : UserEvent() {
        override fun accept(visitor: UserEventVisitor) = visitor.visit(this)
    }
    object B : UserEvent() {
        override fun accept(visitor: UserEventVisitor) = visitor.visit(this)
    }
    object C : UserEvent() {
        override fun accept(visitor: UserEventVisitor) = visitor.visit(this)
    }
    object D : UserEvent() {
        override fun accept(visitor: UserEventVisitor) = visitor.visit(this)
    }

    abstract fun accept(visitor: UserEventVisitor)
}


class ConcreteVisitor : UserEventVisitor {
    override fun visit(event: UserEvent.A) {
        // 处理 EventA
        println("Handling Event A through Visitor")
    }

    override fun visit(event: UserEvent.B) {
        // 处理 EventB
        println("Handling Event B through Visitor")
    }

    override fun visit(event: UserEvent.C) {
        // 处理 EventC
        println("Handling Event C through Visitor")
    }

    override fun visit(event: UserEvent.D) {
        // 处理 EventD
        println("Handling Event D through Visitor")
    }
}

fun main() {
    val eventA = UserEvent.A
    val visitor = ConcreteVisitor()
    eventA.accept(visitor) // Handles Event A through Visitor
}

说明: UserEventVisitor 定义了针对每种 UserEventvisit 方法,每个 UserEvent 的实现类中实现 accept 方法,接收一个 UserEventVisitor 作为参数,然后调用该 visitor 相应的方法。

步骤:

  1. 定义一个访问者接口,包含针对每个事件类型的访问方法。
  2. 在事件类中,添加一个接受访问者对象的方法(通常称为 accept)。
  3. 创建具体的访问者类,实现访问者接口中的方法。

以上三种方法都能有效避免 when 代码块变得臃肿,选择哪一种取决于具体的使用场景和个人偏好。 多态和接口适用于需要在不同的事件类型上执行相同操作的场景; 函数式编程适用于需要将事件处理逻辑解耦的场景; Visitor 模式适用于需要在不修改事件类的情况下添加新操作的场景。