扩展函数与高阶函数在架构模式中的应用
今天我们来聊聊架构模式。说实话,很多Android开发者对MVP、MVVM、MVI这些概念并不陌生,但真正用起来总觉得代码量太大、模板代码太多。我个人习惯用扩展函数和高阶函数来简化这些架构模式,效果还不错。
你想想看,架构模式的核心是什么?说白了就是分离关注点。但分离带来的代价就是多了很多接口、回调、模板代码。扩展函数和高阶函数正好能帮我们解决这个问题。
MVP的扩展函数简化
MVP模式里最烦人的是什么?我觉得是View和Presenter之间的接口定义。每个页面都要写一个View接口,然后Presenter持有这个接口的引用。代码量翻倍不说,维护起来也麻烦。
我在项目中遇到过这样一个场景:一个列表页面,需要展示数据、显示加载状态、处理错误。如果用传统MVP,至少要写三个接口方法。但用扩展函数,我们可以这样简化:
// 传统MVP的View接口
interface UserListView {
fun showUsers(users: List<User>)
fun showLoading()
fun hideLoading()
fun showError(message: String)
}
// 用扩展函数简化
fun <T> BaseView.showData(
data: T?,
loading: Boolean = false,
error: String? = null,
onSuccess: (T) -> Unit,
onError: ((String) -> Unit)? = null
) {
if (loading) {
showLoading()
return
}
error?.let {
onError?.invoke(it) ?: showError(it)
return
}
data?.let { onSuccess(it) }
}
核心思路:把View接口的方法封装成扩展函数,用高阶函数替代接口回调。这样Presenter调用时只需要传一个lambda,代码量减少一半。
嗯,这里要注意一点:扩展函数不能覆盖已有的方法。所以如果你的View接口里已经有showLoading()方法,扩展函数里就不能再定义同名的了。我一般会在基类里统一处理这些通用逻辑。
MVVM的扩展函数
MVVM模式里,LiveData或StateFlow的使用频率很高。但每次都要写observe、collect,也挺烦的。我习惯写一些扩展函数来简化这些操作。
// LiveData的扩展函数
fun <T> LiveData<T>.observeOnce(
owner: LifecycleOwner,
action: (T) -> Unit
) {
observe(owner, object : Observer<T> {
override fun onChanged(value: T) {
action(value)
removeObserver(this)
}
})
}
// StateFlow的扩展函数
fun <T> StateFlow<T>.collectIn(
scope: CoroutineScope,
action: (T) -> Unit
) {
scope.launch {
collect { action(it) }
}
}
// 更实用的:自动处理Loading状态
fun <T> LiveData<UiState<T>>.observeState(
owner: LifecycleOwner,
onSuccess: (T) -> Unit,
onLoading: (() -> Unit)? = null,
onError: ((String) -> Unit)? = null
) {
observe(owner) { state ->
when (state) {
is UiState.Loading -> onLoading?.invoke()
is UiState.Success -> onSuccess(state.data)
is UiState.Error -> onError?.invoke(state.message)
}
}
}
我的经验:在MVVM里,我习惯把UI状态封装成一个sealed class,然后用扩展函数统一处理。这样ViewModel里只需要发射状态,View层只需要观察状态,中间的逻辑全部由扩展函数搞定。
为什么会这样设计?因为MVVM的核心是数据驱动,ViewModel不应该知道View的存在。用扩展函数处理状态分发,既保持了ViewModel的纯净,又简化了View层的代码。
MVI的DSL构建
MVI模式是我最近比较喜欢的架构。它的核心是单向数据流:View发送Intent,ViewModel处理Intent并更新State,View根据State渲染UI。但MVI的代码量也不小,尤其是定义Intent和State的时候。
用DSL来构建MVI的组件,效果出奇的好。我曾经在一个中型项目里尝试过,代码量减少了大概40%。
// MVI的DSL构建
class MviViewModel<S : Any, I : Any>(
initialState: S,
private val reducer: (S, I) -> S
) : ViewModel() {
private val _state = MutableStateFlow(initialState)
val state: StateFlow<S> = _state.asStateFlow()
fun dispatch(intent: I) {
_state.update { reducer(it, intent) }
}
}
// DSL构建器
fun <S : Any, I : Any> mviViewModel(
initialState: S,
reducer: (S, I) -> S
): MviViewModel<S, I> = MviViewModel(initialState, reducer)
// 使用示例
class CounterViewModel : ViewModel() {
// 定义State
data class CounterState(
val count: Int = 0,
val isLoading: Boolean = false
)
// 定义Intent
sealed class CounterIntent {
object Increment : CounterIntent()
object Decrement : CounterIntent()
object Reset : CounterIntent()
}
// 用DSL构建ViewModel
val viewModel = mviViewModel(CounterState()) { state, intent ->
when (intent) {
is CounterIntent.Increment -> state.copy(count = state.count + 1)
is CounterIntent.Decrement -> state.copy(count = state.count - 1)
is CounterIntent.Reset -> state.copy(count = 0)
}
}
}
避坑指南:我曾经在MVI里把reducer写得太复杂,一个reducer里处理了七八种Intent。结果代码变得很难维护。后来我学乖了,每个reducer只处理一种Intent,或者把复杂的逻辑拆成多个小reducer。
DSL的好处是什么?你想想看,它把ViewModel的创建过程封装成了一个声明式的API。你只需要告诉它初始状态和状态转换逻辑,剩下的它帮你搞定。这不就是DSL的魅力吗?
三种架构的对比
| 架构模式 | 扩展函数/高阶函数的作用 | 典型应用场景 | 我的评价 |
|---|---|---|---|
| MVP | 简化View接口,用lambda替代回调 | 列表页面、表单页面 | 适合中小型项目,扩展函数能减少模板代码 |
| MVVM | 封装LiveData/StateFlow的观察逻辑 | 数据驱动页面、复杂状态管理 | 最常用,扩展函数让数据流更清晰 |
| MVI | DSL构建ViewModel和Reducer | 状态复杂、需要可追溯性的场景 | 代码量最少,但学习曲线稍陡 |
说实话,这三种架构没有绝对的好坏。我个人的选择标准是:项目规模小用MVP,中等规模用MVVM,状态特别复杂用MVI。但不管用哪种,扩展函数和高阶函数都能帮我们写出更简洁的代码。
最后说一句,扩展函数和高阶函数只是工具,不是银弹。用得好能让代码更优雅,用得不好反而会增加复杂度。我的建议是:先从简单的场景开始,比如封装一个Loading状态的处理,慢慢扩展到整个架构层。
总结一下:
- MVP:用扩展函数简化View接口,用高阶函数替代回调
- MVVM:用扩展函数封装数据观察逻辑,统一处理UI状态
- MVI:用DSL构建ViewModel和Reducer,让状态管理更声明式
嗯,今天就聊到这里。这些技巧我在实际项目中都用过,效果确实不错。如果你在项目中遇到架构相关的代码冗余问题,不妨试试用扩展函数和高阶函数来优化一下。