close

這是 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,響應式編程似乎有一統天下的趨勢。未來會怎麼樣,我們拭目以待。

參考資料

iPlayground 2019 | 漫談 iOS 架構:MVC / MVVM / VIPER 與 Redux —— Nelson 著
https://www.youtube.com/watch?v=qDyb6AkX0s0
關於MVC/MVP/MVVM的一些錯誤認識 —— wfwf 著
https://mp.weixin.qq.com/s/chcF5vRnFTvSlPti9mfa6A
MVVM 進階版:MVI 架構了解一下~——程序員江同學著
https://juejin.cn/post/7022624191723601928
MVI 架構更佳實踐:支持 LiveData 屬性監聽——程序員江同學著
https://juejin.cn/post/7025222741322121223

END


往期推薦



聊聊職場中最重要的兩個核心能力

你真的會休息嗎?如何開始正確的休息?

國慶返工日,捲起來!

如何系統構建你的思維認知模型?

如何從零到一掌控習慣?

點擊下方卡片關注JsonChao,為你構建一套

未來技術人必備的底層能力系統


▲點擊上方卡片關注JsonChao,構建一套

未來Android開發必備的知識體系

歡迎把文章分享到朋友圈

年度成長社群

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()