MVI 架构
近日,600多名谷歌員工簽署了一份宣言,反對該公司強制接種新冠疫苗。這對谷歌領(lǐng)導層構(gòu)成了最新挑戰(zhàn),因為該公司即將迎來讓員工重返辦公室的關(guān)鍵最后期限。拜登政府已下令,員工人數(shù)在百人以上的美國公司,需要在2022年1月4日前確保員工全面接種疫苗或定期檢測新冠肺炎。泄露的谷歌內(nèi)部文件顯示,該公司要求其超過15萬名員工在12月3日之前將疫苗接種狀態(tài)上傳到其內(nèi)部系統(tǒng)上,無論他們是否計劃復崗。谷歌還表示,所有直接或間接履行政府合同的員工都必須接種疫苗,即使他們在家工作也是如此。
/ 作者簡介 /
本篇文章來自RicardoMJiang的投稿,文章主要分享了作者對目前架構(gòu)進行總結(jié)并延伸的相關(guān)內(nèi)容,相信會對大家有所幫助!同時也感謝作者貢獻的精彩文章。
/ 前言 /
Android開發(fā)發(fā)展到今天已經(jīng)相當成熟了,各種架構(gòu)大家也都耳熟能詳,如MVC,MVP,MVVM等,其中MVVM更是被官方推薦,成為Android開發(fā)中的顯學。
不過軟件開發(fā)中沒有銀彈,MVVM架構(gòu)也不是盡善盡美的,在使用過程中也會有一些不太方便之處,而MVI可以很好的解決一部分MVVM的痛點。
本文主要包括以下內(nèi)容
MVC,MVP,MVVM等經(jīng)典架構(gòu)介紹
MVI架構(gòu)到底是什么?
MVI架構(gòu)實戰(zhàn)
需要重點指出的是,標題中說MVI架構(gòu)是MVVM的進階版是指MVI在MVVM非常相似,并在其基礎(chǔ)上做了一定的改良,并不是說MVI架構(gòu)一定比MVVM適合你的項目。各位同學可以在分析比較各個架構(gòu)后,選擇合適項目場景的架構(gòu)
/ 經(jīng)典架構(gòu)介紹 /
MVC架構(gòu)介紹
MVC是個古老的Android開發(fā)架構(gòu),隨著MVP與MVVM的流行已經(jīng)逐漸退出歷史舞臺,我們在這里做一個簡單的介紹,其架構(gòu)圖如下所示:
圖片
MVC架構(gòu)主要分為以下幾部分:
視圖層(View):對應(yīng)于xml布局文件和java代碼動態(tài)view部分
控制層(Controller):主要負責業(yè)務(wù)邏輯,在android中由Activity承擔,同時因為XML視圖功能太弱,所以Activity既要負責視圖的顯示又要加入控制邏輯,承擔的功能過多。
模型層(Model):主要負責網(wǎng)絡(luò)請求,數(shù)據(jù)庫處理,I/O的操作,即頁面的數(shù)據(jù)來源
由于android中xml布局的功能性太弱,Activity實際上負責了View層與Controller層兩者的工作,所以在android中mvc更像是這種形式:
圖片
因此MVC架構(gòu)在android平臺上的主要存在以下問題:
Activity同時負責View與Controller層的工作,違背了單一職責原則
Model層與View層存在耦合,存在互相依賴,違背了最小知識原則
MVP架構(gòu)介紹
由于MVC架構(gòu)在Android平臺上的一些缺陷,MVP也就應(yīng)運而生了,其架構(gòu)圖如下所示:
圖片
MVP架構(gòu)主要分為以下幾個部分:
View層:對應(yīng)于Activity與XML,只負責顯示UI,只與Presenter層交互,與Model層沒有耦合
Presenter層:主要負責處理業(yè)務(wù)邏輯,通過接口回調(diào)View層
Model層:主要負責網(wǎng)絡(luò)請求,數(shù)據(jù)庫處理等操作,這個沒有什么變化
我們可以看到,MVP解決了MVC的兩個問題,即Activity承擔了兩層職責與View層與Model層耦合的問題。但MVP架構(gòu)同樣有自己的問題:
Presenter層通過接口與View通信,實際上持有了View的引用
但是隨著業(yè)務(wù)邏輯的增加,一個頁面可能會非常復雜,這樣就會造成View的接口會很龐大。
MVVM架構(gòu)介紹
MVVM 模式將 Presenter 改名為 ViewModel,基本上與 MVP 模式完全一致。唯一的區(qū)別是,它采用雙向數(shù)據(jù)綁定(data-binding):View的變動,自動反映在 ViewModel,反之亦然。
MVVM架構(gòu)圖如下所示:
圖片
可以看出MVVM與MVP的主要區(qū)別在于,你不用去主動去刷新UI了,只要Model數(shù)據(jù)變了,會自動反映到UI上。換句話說,MVVM更像是自動化的MVP。
MVVM的雙向數(shù)據(jù)綁定主要通過DataBinding實現(xiàn),不過相信有很多人跟我一樣,是不喜歡用DataBinding的,這樣架構(gòu)就變成了下面這樣。
圖片
View觀察ViewModel的數(shù)據(jù)變化并自我更新,這其實是單一數(shù)據(jù)源而不是雙向數(shù)據(jù)綁定,所以其實MVVM的這一大特性我其實并沒有用到
View通過調(diào)用ViewModel提供的方法來與ViewModel交互
小結(jié)
MVC架構(gòu)的主要問題在于Activity承擔了View與Controller兩層的職責,同時View層與Model層存在耦合
MVP引入Presenter層解決了MVC架構(gòu)的兩個問題,View只能與Presenter層交互,業(yè)務(wù)邏輯放在Presenter層
MVP的問題在于隨著業(yè)務(wù)邏輯的增加,View的接口會很龐大,MVVM架構(gòu)通過雙向數(shù)據(jù)綁定可以解決這個問題
MVVM與MVP的主要區(qū)別在于,你不用去主動去刷新UI了,只要Model數(shù)據(jù)變了,會自動反映到UI上。換句話說,MVVM更像是自動化的MVP。
MVVM的雙向數(shù)據(jù)綁定主要通過DataBinding實現(xiàn),但有很多人(比如我)不喜歡用DataBinding,而是View通過LiveData等觀察ViewModle的數(shù)據(jù)變化并自我更新,這其實是單一數(shù)據(jù)源而不是雙向數(shù)據(jù)綁定
/ MVI架構(gòu)到底是什么 /
MVVM架構(gòu)有什么不足?
要了解MVI架構(gòu),我們首先來了解下MVVM架構(gòu)有什么不足,相信使用MVVM架構(gòu)的同學都有如下經(jīng)驗,為了保證數(shù)據(jù)流的單向流動,LiveData向外暴露時需要轉(zhuǎn)化成immutable的,這需要添加不少模板代碼并且容易遺忘,如下所示:
class TestViewModel : ViewModel() {
//為保證對外暴露的LiveData不可變,增加一個狀態(tài)就要添加兩個LiveData變量
private val _pageState: MutableLiveData = MutableLiveData()
val pageState: LiveData = _pageState
private val _state1: MutableLiveData = MutableLiveData()
val state1: LiveData = _state1
private val _state2: MutableLiveData = MutableLiveData()
val state2: LiveData = _state2
//…
}
如上所示,如果頁面邏輯比較復雜,ViewModel中將會有許多全局變量的LiveData,并且每個LiveData都必須定義兩遍,一個可變的,一個不可變的。這其實就是我通過MVVM架構(gòu)寫比較復雜頁面時最難受的點。其次就是View層通過調(diào)用ViewModel層的方法來交互的,View層與ViewModel的交互比較分散,不成體系。
小結(jié)一下,在我的使用中,MVVM架構(gòu)主要有以下不足:
為保證對外暴露的LiveData是不可變的,需要添加不少模板代碼并且容易遺忘
View層與ViewModel層的交互比較分散零亂,不成體系
MVI架構(gòu)是什么?
MVI與MVVM很相似,其借鑒了前端框架的思想,更加強調(diào)數(shù)據(jù)的單向流動和唯一數(shù)據(jù)源,架構(gòu)圖如下所示:
圖片
其主要分為以下幾部分:
Model:與MVVM中的Model不同的是,MVI的Model主要指UI狀態(tài)(State)。例如頁面加載狀態(tài)、控件位置等都是一種UI狀態(tài)
View:與其他MVX中的View一致,可能是一個Activity或者任意UI承載單元。MVI中的View通過訂閱Intent的變化實現(xiàn)界面刷新(注意:這里不是Activity的Intent)
Intent:此Intent不是Activity的Intent,用戶的任何操作都被包裝成Intent后發(fā)送給Model層進行數(shù)據(jù)請求
單向數(shù)據(jù)流
MVI強調(diào)數(shù)據(jù)的單向流動,主要分為以下幾步:
用戶操作以Intent的形式通知Model
Model基于Intent更新State
View接收到State變化刷新UI
數(shù)據(jù)永遠在一個環(huán)形結(jié)構(gòu)中單向流動,不能反向流動:
圖片
上面簡單的介紹了下MVI架構(gòu),下面我們一起來看下具體是怎么使用MVI架構(gòu)的。
/ MVI架構(gòu)實戰(zhàn) /
總體架構(gòu)圖
圖片
我們使用ViewModel來承載MVI的Model層,總體結(jié)構(gòu)也與MVVM類似,主要區(qū)別在于Model與View層交互的部分:
Model層承載UI狀態(tài),并暴露出ViewState供View訂閱,ViewState是個data class,包含所有頁面狀態(tài)
View層通過Action更新ViewState,替代MVVM通過調(diào)用ViewModel方法交互的方式
MVI實例介紹
添加ViewState與ViewEvent
ViewState承載頁面的所有狀態(tài),ViewEvent則是一次性事件,如Toast等,如下所示:
data class MainViewState(val fetchStatus: FetchStatus, val newsList: List)
sealed class MainViewEvent {
data class ShowSnackbar(val message: String) : MainViewEvent()
data class ShowToast(val message: String) : MainViewEvent()
}
我們這里ViewState只定義了兩個,一個是請求狀態(tài),一個是頁面數(shù)據(jù)
ViewEvent也很簡單,一個簡單的密封類,顯示Toast與Snackbar
ViewState更新
class MainViewModel : ViewModel() {
private val _viewStates: MutableLiveData = MutableLiveData()
val viewStates = _viewStates.asLiveData()
private val _viewEvents: SingleLiveEvent = SingleLiveEvent()
val viewEvents = _viewEvents.asLiveData()
}
如上所示:
我們只需定義ViewState與ViewEvent兩個State,后續(xù)增加狀態(tài)時在data class中添加即可,不需要再寫模板代碼
ViewEvents是一次性的,通過SingleLiveEvent實現(xiàn),當然你也可以用Channel當來實現(xiàn)
當狀態(tài)更新時,通過emit來更新狀態(tài)
View監(jiān)聽ViewState
private fun initViewModel() {viewModel.viewStates.observe(this) {renderViewState(it)}viewModel.viewEvents.observe(this) {renderViewEvent(it)} }如上所示,MVI使用ViewState對State集中管理,只需要訂閱一個ViewState便可獲取頁面的所有狀態(tài),相對MVVM減少了不少模板代碼。
View通過Action更新State
class MainActivity : AppCompatActivity() {
private fun initView() {
fabStar.setOnClickListener {
viewModel.dispatch(MainViewAction.FabClicked)
}
}
}
class MainViewModel : ViewModel() {
fun dispatch(action: MainViewAction) =
reduce(viewStates.value, action)
}
如上所示,View通過Action與ViewModel交互,通過Action通信,有利于View與ViewModel之間的進一步解耦,同時所有調(diào)用以Action的形式匯總到一處,也有利于對行為的集中分析和監(jiān)控。
/ 總結(jié) /
本文主要介紹了MVC,MVP,MVVM與MVI架構(gòu),目前MVVM是官方推薦的架構(gòu),但仍然有以下幾個痛點:
MVVM與MVP的主要區(qū)別在于雙向數(shù)據(jù)綁定,但由于很多人(比如我)并不喜歡使用DataBindg,其實并沒有使用MVVM雙向綁定的特性,而是單一數(shù)據(jù)源
當頁面復雜時,需要定義很多State,并且需要定義可變與不可變兩種,狀態(tài)會以雙倍的速度膨脹,模板代碼較多且容易遺忘
View與ViewModel通過ViewModel暴露的方法交互,比較零亂難以維護
而MVI可以比較好的解決以上痛點,它主要有以下優(yōu)勢:
強調(diào)數(shù)據(jù)單向流動,很容易對狀態(tài)變化進行跟蹤和回溯
使用ViewState對State集中管理,只需要訂閱一個ViewState便可獲取頁面的所有狀態(tài),相對 MVVM 減少了不少模板代碼
ViewModel通過ViewState與Action通信,通過瀏覽ViewState和Aciton定義就可以理清ViewModel的職責,可以直接拿來作為接口文檔使用。
當然MVI也有一些缺點,比如:
所有的操作最終都會轉(zhuǎn)換成State,所以當復雜頁面的State容易膨脹
State是不變的,因此每當State需要更新時都要創(chuàng)建新對象替代老對象,這會帶來一定內(nèi)存開銷
軟件開發(fā)中沒有銀彈,所有架構(gòu)都不是完美的,有自己的適用場景,讀者可根據(jù)自己的需求選擇使用。
但通過以上的分析與介紹,我相信使用MVI架構(gòu)代替沒有使用DataBinding的MVVM是一個比較好的選擇~
總結(jié)
- 上一篇: 华为AR engine 应用开发学习教程
- 下一篇: 《Python程序设计》教学大纲