函数式架构
在 Android 开发中,引入 函数式编程 (FP) 理念不仅仅是为了使用 Lambda 表达式,更是为了构建 低耦合、高内聚、可预测 的系统。函数式架构的核心在于通过 不可变性 和 纯函数 来控制复杂度,尤其擅长解决并发环境下的状态同步问题。
核心支柱:纯粹与不可变
函数式架构的基石是建立可信赖的最小单元。
纯函数
一个函数如果满足以下两个条件,即为纯函数:
- 引用透明:给定相同的输入,永远产生相同的输出。
- 无副作用:不修改外部状态(全局变量、文件、数据库等)。
// ✅ 纯函数:结果仅依赖参数
fun calculateTotal(price: Double, tax: Double): Double = price * (1 + tax)
// ❌ 非纯函数:依赖外部可变状态,结果不可预测
var globalRate = 0.1
fun calculateTotalImpure(price: Double): Double = price * (1 + globalRate)不可变数据
在多线程环境中,可变状态是万恶之源。Kotlin 提供了 val 和 data class 的 copy() 机制来支持 写时复制 (COW)。
data class User(val name: String, val age: Int)
// 修改状态 = 创建新对象
val oldUser = User("Alice", 25)
val newUser = oldUser.copy(age = 26) // 旧对象保持不变铁路导向编程 (ROP)
处理业务流程中的错误通常会导致“嵌套地狱”或异常滥用。ROP (Railway Oriented Programming) 模式将函数看作铁轨,拥有“成功”和“失败”两条平行轨道。数据在轨道上流动,一旦进入失败轨道,就会绕过后续步骤直达终点。
Kotlin 中的 ROP 实现
标准库的 Result<T> 类型是实现 ROP 的最简单工具。
fun processPayment(orderId: String): Result<String> {
return findOrder(orderId) // Step 1: 查找订单 -> Result<Order>
.mapCatching { validate(it) } // Step 2: 校验 -> Result<Order> (自动捕获异常)
.mapCatching { charge(it) } // Step 3: 扣款 -> Result<Receipt>
.map { "Success: ${it.id}" } // Step 4: 转换结果
// 任何一步失败,都会直接跳过后续步骤,返回 Result.Failure
}fun processPaymentLegacy(orderId: String): String {
try {
val order = findOrder(orderId)
validate(order) // 可能抛异常
val receipt = charge(order) // 可能抛异常
return "Success: ${receipt.id}"
} catch (e: Exception) {
return "Error: ${e.message}" // 异常流控制
}
}进阶工具:Either
虽然 Result 很好用,但它只能表示 Throwable 类型的错误。更复杂的业务场景通常使用 Either<Left, Right>(通常由 Arrow 库提供),其中 Left 代表业务错误(如余额不足),Right 代表成功值。
// 伪代码示例:Either 的语义更丰富
fun validateUser(user: User): Either<ValidationError, User> {
return if (user.isValid()) Either.Right(user)
else Either.Left(ValidationError.InvalidEmail)
}声明式 UI 状态管理 (MVI)
Jetpack Compose 的出现让函数式架构在 UI 层大放异彩。MVI (Model-View-Intent) 模式本质上是一个巨大的 fold 操作:State + Event -> NewState。
状态归约器 (Reducer)
Reducer 是一个纯函数,它接收当前状态和用户意图,计算出新状态。
// 状态定义 (不可变)
data class UiState(val count: Int = 0, val isLoading: Boolean = false)
// 意图定义 (密封接口)
sealed interface UiIntent {
data object Increment : UiIntent
data object ToggleLoading : UiIntent
}
// ✅ 纯函数 Reducer:逻辑极其清晰,易于测试
fun reduce(currentState: UiState, intent: UiIntent): UiState {
return when (intent) {
UiIntent.Increment -> currentState.copy(count = currentState.count + 1)
UiIntent.ToggleLoading -> currentState.copy(isLoading = !currentState.isLoading)
}
}领域建模与代数数据类型
函数式架构强调使用类型系统来让“非法状态不可表示”。Kotlin 的 sealed class 是实现 和类型 (Sum Type) 的完美工具。
案例:加载状态建模
sealed interface ScreenState {
data object Loading : ScreenState
data class Content(val data: List<String>) : ScreenState
data class Error(val message: String) : ScreenState
}
// 编译器强制处理所有分支,且状态互斥data class ScreenStateBad(
val isLoading: Boolean,
val data: List<String>?,
val error: String?
)
// ❌ 可能出现 isLoading=true 且 error!=null 的非法组合副作用管理
在纯函数的世界里,如何处理 IO、数据库读写等副作用?策略是将副作用推迟到系统的边缘(通常是 ViewModel 或 UseCase 层)执行,核心逻辑保持纯净。
- 核心层:包含纯函数、领域模型。负责“决定做什么”。
- 外壳层:包含副作用、框架代码。负责“执行操作”。
IO 隔离示例
// 核心层:纯逻辑,只返回"意图",不执行
fun planPurchase(balance: Double, price: Double): PurchaseDecision {
return if (balance >= price) PurchaseDecision.Buy
else PurchaseDecision.Reject("Not enough money")
}
// 外壳层:处理副作用
fun executePurchase() {
val decision = planPurchase(wallet.balance, item.price)
when (decision) {
is PurchaseDecision.Buy -> api.buyItem() // 副作用发生在这里
is PurchaseDecision.Reject -> ui.showToast(decision.reason)
}
}推荐库与生态
- Arrow: Kotlin 函数式编程的瑞士军刀。提供了
Either,Option,Reader,Effect Scopes等高级数据类型。 - Kotlinx Immutable Collections: Jetpack Compose 推荐的集合库。提供
PersistentList等持久化数据结构,修改集合时共享内存结构而非全量复制,性能远高于标准库的toMutableList()。
学习建议
不必在一夜之间将整个 App 重构为 Haskell 风格。可以从 不可变状态、消除空指针 (Option/Result) 以及 抽离纯函数 这三点开始,逐步引入函数式思维。