這是 JsonChao 的第256期分享
為了優化代碼設計,業界先後提出了 MVC、MVP、MVVM 和 MVI 等架構設計。這四個模式討論是 「如何管理 UI」 這個話題,只是實現的細節不同。
最開始是沒有採用任何模式的狀態,不管是視圖代碼還是表現邏輯全都寫在 Activity 裡面,很明顯這樣的代碼耦合度非常高,難以進行維護和測試,可讀性也不好。
提示:耦合度高是現象,關注點分離是手段,易維護性和易測試性是結果,模式是可復用的經驗。
1. MVC
MVC 其實是 Android 默認的設計,MVC 里將代碼分為三個部分:
View:Layout XML 文件;
Model:負責管理業務數據邏輯,如網絡請求、數據庫處理;
Controller:Activity 負責處理表現邏輯。
MVC 初步解決了 Activity 代碼太多的問題,但也有缺點:我們的初衷 Activity / Fragment 是只處理表現邏輯的部分 ,但現實是 Activity 天然不可避免要處理 UI,也要處理用戶交互,說明 Activity 本身天然承擔了 View 的角色。那麼這個架構就會造成 Activity 里糅合了視圖和業務的代碼,分離程度不夠。
2. MVP
為了將 Activity 中的表現邏輯徹底分離出來,業界提出了 MVP 的設計。MVP 同樣將代碼劃分為三個部分:
View:Activity 和 Layout XML 文件;
Model:負責管理業務數據邏輯,如網絡請求、數據庫處理;
Presenter:負責處理表現邏輯。
在實現細節上,View 和 Presenter 中間會定義一個協議接口 Contract,這個接口會約定 View 如何向 Presenter 發指令和 Presenter 如何 Callback 給 View。這樣的架構里 Activity 不再有表現邏輯的部分,Activity 作為 View 的角色只處理和 UI 有關的事情。但還是存在一些缺點:
雙向依賴:View 和 Presenter 是雙向依賴的,一旦 View 層做出改變,相應地 Presenter 也需要做出調整。在業務語境下,View 層變化是大概率事件;
內存泄漏風險:Presenter 持有 View 層的引用,當用戶關閉了 View 層,但 Model 層仍然在進行耗時操作,就會有內存泄漏風險。雖然有解決辦法,但還是存在風險點和複雜度(弱引用 / onDestroy() 回收 Presenter)。
協議接口類膨脹:View 層和 Presenter 層的交互需要定義接口方法,當交互非常複雜時,需要定義很多接口方法和回調方法,也不好維護。
3. MVVM
MVVM 模式改動在於中間的 Presenter 改為 ViewModel,MVVM 同樣將代碼劃分為三個部分:
View:Activity 和 Layout XML 文件,與 MVP 中 View 的概念相同;
Model:負責管理業務數據邏輯,如網絡請求、數據庫處理,與 MVP 中 Model 的概念相同;
ViewModel:存儲視圖狀態,負責處理表現邏輯,並將數據設置給可觀察數據容器。
在實現細節上,View 和 Presenter 從雙向依賴變成 View 可以向 ViewModel 發指令,但 ViewModel 不會直接向 View 回調,而是讓 View 通過觀察者的模式去監聽數據的變化,有效規避了 MVP 雙向依賴的缺點。但 MVVM 本身也存在一些缺點:
多數據流:View 與 ViewModel 的交互分散,缺少唯一修改源,不易於追蹤;
LiveData 膨脹:複雜的頁面需要定義多個 MutableLiveData,並且都需要暴露為不可變的 LiveData。
DataBinding、ViewModel 和 LiveData 等組件是 Google 為了幫助我們實現 MVVM 模式提供的架構組件,它們並不是 MVVM 的本質,只是實現上的工具。
Lifecycle:生命周期狀態回調;
LiveData:可觀察的數據存儲類;
databinding:可以自動同步 UI 和 data,不用再 findviewById();
ViewModel:存儲界面相關的數據,這些數據不會在手機旋轉等配置改變時丟失。
4. MVI
MVI 模式的改動在於將 View 和 ViewModel 之間的多數據流改為基於 ViewState 的單數據流。MVI 將代碼分為以下四個部分:
View:Activity 和 Layout XML 文件,與 MVVM 中 View 的概念相同;
Intent:定義數據操作,是將數據傳到 Model 的唯一來源,相比 MVVM 是新的概念;
ViewModel:存儲視圖狀態,負責處理表現邏輯,並將 ViewState 設置給可觀察數據容器;
ViewState:一個數據類,包含頁面狀態和對應的數據。
在實現細節上,View 和 ViewModel 之間的多個交互(多 LiveData 數據流)變成了單數據流。無論 View 有多少個視圖狀態,只需要訂閱一個 ViewState 便可以獲取所有狀態,再根據 ViewState 去響應。當然,實踐中應該根據狀態之間的關聯程度來決定數據流的個數,不應該為了使用 MVI 模式而強行將多個無關的狀態壓縮在同一個數據流中。
唯一可信源:數據只有一個來源(ViewModel),與 MVVM 的思想相同;
單數據流:View 和 ViewModel 之間只有一個數據流,只有一個地方可以修改數據,確保數據是安全穩定的。並且 View 只需要訂閱一個 ViewState 就可以獲取所有狀態和數據,相比 MVVM 是新的特性;
響應式:ViewState 包含頁面當前的狀態和數據,View 通過訂閱 ViewState 就可以完成頁面刷新,相比於 MVVM 是新的特性。
但 MVI 本身也存在一些缺點:
State 膨脹:所有視圖變化都轉換為 ViewState,還需要管理不同狀態下對應的數據。實踐中應該根據狀態之間的關聯程度來決定使用單流還是多流;
內存開銷:ViewState 是不可變類,狀態變更時需要創建新的對象,存在一定內存開銷;
局部刷新:View 根據 ViewState 響應,不易實現局部 Diff 刷新,可以使用 Flow#distinctUntilChanged() 來刷新來減少不必要的刷新。
不過,MVI 並不是一個全新的設計模式,其背後設計理念與 Redux 模式如出一轍。在 Redux 里完全可以找到與 MVI 相同的各個要素,而且明顯 Redux 的命名方式更加清晰無歧義,小夥伴們知道 Model - View - Intent 這個命名方式的原始出處的話,可以告訴我一聲。
View - View
Action - Intent
Store - ViewModel
State - ViewState
Reducer - Model
// 1、ViewModelclass MainViewModel: ViewModel() { private val mModel = MainModel() val mIntent = Channel<MainIntent>(Channel.UNLIMITED) private val _state = MutableStateFlow<MainViewState>(MainViewState.Idle) val state: StateFlow<MainViewState> get() = _state init { viewModelScope.launch { mIntent.consumeAsFlow().collect { when (it) { is MainIntent.FetchNew -> fetchNews() } } } } private fun fetchNews() { viewModelScope.launch { _state.value = MainViewState.Loading _state.value = try { MainViewState.News(mModel.fetchNews()) } catch (e: Exception) { MainViewState.Error(e.localizedMessage) } } }}// 2、ViewStatesealed class MainViewState { object Idle : MainViewState() object Loading : MainViewState() data class News(val news: List<New>) : MainViewState() data class Error(val error: String?) : MainViewState()}// 3、Intentsealed class MainIntent { object FetchNew : MainIntent()}// 4、Viewclass MainActivity : AppCompatActivity() { private lateinit var mainViewModel: MainViewModel private fun observeViewModel() { lifecycleScope.launch { mainViewModel.state.collect { when (it) { is MainViewState.Idle -> { } is MainViewState.Loading -> { } is MainViewState.News -> { renderList(it.news) } is MainViewState.Error -> { } } } } } private fun renderList(news: List<New>) { // do something }}5. MVP、MVVM 和 MVI 的對比
MVVM 和 MVP 的思想是相同的,最本質的概念就是 Activity 里做的事情太多了,所以要把 Activity 中與 UI 無關的部分抽離出來,交給別人做。這個 「別人」 在 MVP 里叫作 Presenter,在 MVVM 里叫作 ViewModel。而不論是 MVP 中的約定接口,還是 ViewModel 里的觀察者模式,這些都是實現上的細節而已。
MVI 與前者的主要區別不在於強調嚴格的單向數據流,而在於從命令式的開發模式,轉變為響應式的開發模式。我們並不是說越新潮,越複雜的架構就是最好的,只有合適的架構才是最好的。但是不可否認,從 React 到 Flutter,從 MVI 到 Compose,響應式編程似乎有一統天下的趨勢。未來會怎麼樣,我們拭目以待。
參考資料 END 往期推薦 聊聊職場中最重要的兩個核心能力 你真的會休息嗎?如何開始正確的休息? 國慶返工日,捲起來! 如何系統構建你的思維認知模型? 如何從零到一掌控習慣?
點擊下方卡片關注JsonChao,為你構建一套
未來技術人必備的底層能力系統
▲點擊上方卡片關注JsonChao,構建一套
未來Android開發必備的知識體系
歡迎把文章分享到朋友圈