原文:《REACTIVE APPS WITH MODEL-VIEW-INTENT - PART1 - MODEL》
作者:Hannes Dorfmann
譯者:卻把清梅嗅
有朝一日,我突然發(fā)現(xiàn)我對(duì)于Model
層的定義 全部是錯(cuò)誤的逛绵,更新了認(rèn)知后塌鸯,我發(fā)現(xiàn)曾經(jīng)我在Android
平臺(tái)上主題討論中的那些困惑或者頭痛都消失了卧斟。
從結(jié)果上來(lái)說(shuō),最終我選擇使用 RxJava
和 Model-View-Intent(MVI)
構(gòu)建 響應(yīng)式的APP牡辽,這是我從未有過(guò)的嘗試——盡管在這之前我開(kāi)發(fā)的APP也是響應(yīng)式的俯渤,但 響應(yīng)式編程 的體現(xiàn)與這次實(shí)踐相比,完全無(wú)法相提并論棒坏,在接下來(lái)我將要講述的一系列文章中,你也會(huì)感受到這些遭笋。但作為系列文章的開(kāi)始坝冕,我想先闡述一個(gè)觀點(diǎn):
所謂的
Model
層到底是什么,我之前對(duì)Model
層的定義出現(xiàn)了什么問(wèn)題瓦呼?
我為什么說(shuō) 我對(duì)Model
層有著錯(cuò)誤的理解和使用方式 呢喂窟?當(dāng)然测暗,現(xiàn)在有很多架構(gòu)模式將View
層和Model
層進(jìn)行了分離,至少在Android
開(kāi)發(fā)的領(lǐng)域磨澡,最著名的當(dāng)屬Model-View-Controller (MVC)
碗啄、Model-View-Presenter (MVP)
和Model-View-ViewModel (MVVM)
——你注意到了嗎?這些架構(gòu)模式中稳摄,Model
都是不可或缺的一環(huán)稚字,但我意識(shí)到 在絕大數(shù)情況下,我根本沒(méi)有Model
厦酬。
舉例來(lái)說(shuō)胆描,一個(gè)簡(jiǎn)單的從后端拉取Person
列表情況下,傳統(tǒng)的MVP
實(shí)現(xiàn)方式應(yīng)該是這樣的:
class PersonsPresenter extends Presenter<PersonsView> {
public void load(){
getView().showLoading(true); // 展示一個(gè) ProgressBar
backend.loadPersons(new Callback(){
public void onSuccess(List<Person> persons){
getView().showPersons(persons); // 展示用戶列表
}
public void onError(Throwable error){
getView().showError(error); // 展示錯(cuò)誤信息
}
});
}
}
但是仗阅,這段代碼中的Model
到底是指什么呢昌讲?是指后臺(tái)的網(wǎng)絡(luò)請(qǐng)求嗎?不减噪,那只是業(yè)務(wù)邏輯短绸。是指請(qǐng)求結(jié)果的用戶列表嗎?不筹裕,它和ProgressBar醋闭、錯(cuò)誤信息的展示一樣,僅僅只代表了View
層所能展示內(nèi)容的一小部分而已饶碘。
那么,Model
層究竟是指什么呢馒吴?
從我個(gè)人理解來(lái)說(shuō),Model
類(lèi)應(yīng)該定義成這樣:
class PersonsModel {
// 在真實(shí)的項(xiàng)目中扎运,需要定義為私有的
// 并且我們需要通過(guò)getter和setter來(lái)訪問(wèn)它們
final boolean loading;
final List<Person> persons;
final Throwable error;
public(boolean loading, List<Person> persons, Throwable error){
this.loading = loading;
this.persons = persons;
this.error = error;
}
}
這樣的實(shí)現(xiàn),Presenter
層應(yīng)該這樣實(shí)現(xiàn):
class PersonsPresenter extends Presenter<PersonsView> {
public void load(){
getView().render( new PersonsModel(true, null, null) ); // 展示一個(gè) ProgressBar
backend.loadPersons(new Callback(){
public void onSuccess(List<Person> persons){
getView().render( new PersonsModel(false, persons, null) ); // 展示用戶列表
}
public void onError(Throwable error){
getView().render( new PersonsModel(false, null, error) ); // 展示錯(cuò)誤信息
}
});
}
}
現(xiàn)在饮戳,View
層持有了一個(gè)Model
豪治,并且能夠借助它對(duì)屏幕上的控件進(jìn)行rendered
(渲染)。這并非什么新鮮的概念扯罐,Trygve Reenskaug
在1979年時(shí)负拟,其對(duì)最初版本的MVC
定義中具有相似的概念:View
觀察Model
的變化。
然而歹河,MVC
這個(gè)術(shù)語(yǔ)被用來(lái)描述太多種不同的模式掩浙,這些模式與Reenskaug
在1979年制定的模式并不完全相同。比如后端開(kāi)發(fā)人員使用MVC
框架秸歧,iOS有ViewController
厨姚,到了Android
領(lǐng)域MVC
又被如何定義了呢?Activity
是Controller
嗎? 那這樣的話ClickListener
又算什么呢键菱?如今谬墙,MVC
這個(gè)術(shù)語(yǔ)變成了一個(gè)很大的誤區(qū),它錯(cuò)誤地理解和使用了Reenskaug最初制定的內(nèi)容——這個(gè)話題到此為止,再繼續(xù)下去整個(gè)文章就會(huì)失控了拭抬。
言歸正傳部默,Model
的持有將會(huì)解決許多我們?cè)?code>Android開(kāi)發(fā)中經(jīng)常遇到的問(wèn)題:
- 1.狀態(tài)問(wèn)題
- 2.屏幕方向的改變
- 3.在頁(yè)面堆棧中導(dǎo)航
- 4.進(jìn)程終止
- 5.單向數(shù)據(jù)流的不變性
- 6.可調(diào)試和可重現(xiàn)的狀態(tài)
- 7.可測(cè)試性
要討論這些關(guān)鍵的問(wèn)題,我們先來(lái)看看“傳統(tǒng)”的MVP
和MVVM
的實(shí)現(xiàn)代碼中如何處理它們造虎,然后再談Model
如何跳過(guò)這些常見(jiàn)的陷阱傅蹂。
1.狀態(tài)問(wèn)題
響應(yīng)式App,這是最近非常流行的話題累奈,不是嗎贬派?所謂的 響應(yīng)式App 就是 應(yīng)用會(huì)根據(jù)狀態(tài)的改變作出UI的響應(yīng),這句話里有一個(gè)非常好的單詞:狀態(tài)澎媒。什么是狀態(tài)呢搞乏?大多數(shù)時(shí)間里,我們將 狀態(tài) 描述為我們?cè)谄聊恢锌吹降臇|西戒努,例如當(dāng)界面展示ProgressBar
時(shí)的loading state
请敦。
很關(guān)鍵的一點(diǎn)是,我們前端開(kāi)發(fā)人員傾向?qū)W⒂赨I储玫。這不一定是壞事侍筛,因?yàn)橐粋€(gè)好的UI體驗(yàn)決定了用戶是否會(huì)用你的產(chǎn)品,從而決定了產(chǎn)品能否獲得成功撒穷。但是看看上述的MVP
示例代碼(不是使用了PersonModel
的那個(gè)例子)匣椰,這里UI的狀態(tài)由Presenter
進(jìn)行協(xié)調(diào),Presenter
負(fù)責(zé)告訴View
層如何進(jìn)行展示端礼。
MVVM
亦然禽笑,我想在本文中對(duì)MVVM
的兩種實(shí)現(xiàn)方式進(jìn)行區(qū)分:第一種依賴(lài)DataBinding
庫(kù),第二種則依賴(lài)RxJava
蛤奥;對(duì)于依賴(lài)DataBinding
的前者佳镜,其狀態(tài)被直接定義于ViewModel
中:
class PersonsViewModel {
ObservableBoolean loading;
// 省略...
public void load(){
loading.set(true);
backend.loadPersons(new Callback(){
public void onSuccess(List<Person> persons){
loading.set(false);
// 省略其它代碼,比如對(duì)persons進(jìn)行渲染
}
public void onError(Throwable error){
loading.set(false);
// 省略其它代碼凡桥,比如展示錯(cuò)誤信息
}
});
}
}
使用RxJava
實(shí)現(xiàn)MVVM
的方式中蟀伸,其并不依賴(lài)DataBinding
引擎,而是將Observable
和UI的控件進(jìn)行綁定缅刽,例如:
class RxPersonsViewModel {
private PublishSubject<Boolean> loading;
private PublishSubject<List<Person> persons;
private PublishSubject loadPersonsCommand;
public RxPersonsViewModel(){
loadPersonsCommand.flatMap(ignored -> backend.loadPersons())
.doOnSubscribe(ignored -> loading.onNext(true))
.doOnTerminate(ignored -> loading.onNext(false))
.subscribe(persons)
// 實(shí)現(xiàn)方式并不惟一
}
// 在View層訂閱它 (比如 Activity / Fragment)
public Observable<Boolean> loading(){
return loading;
}
// 在View層訂閱它 (比如 Activity / Fragment)
public Observable<List<Person>> persons(){
return persons;
}
// 每當(dāng)觸發(fā)此操作 (即調(diào)用 onNext()) 啊掏,加載Persons數(shù)據(jù)
public PublishSubject loadPersonsCommand(){
return loadPersonsCommand;
}
}
當(dāng)然,這些代碼并非完美衰猛,您的實(shí)現(xiàn)方式可能截然不同脖律;我想說(shuō)明的是,通常在MVP
或者MVVM
中腕侄,狀態(tài) 是由ViewModel
或者Presenter
進(jìn)行驅(qū)動(dòng)的小泉。
這導(dǎo)致下述情況的發(fā)生:
1.業(yè)務(wù)邏輯本身也擁有了狀態(tài)芦疏,
Presenter
(或者ViewModel
)本身也擁有了狀態(tài)(并且,你還需要通過(guò)代碼去同步它們的狀態(tài)使其保持一致)微姊,同時(shí)酸茴,View
可能也有自己的狀態(tài)(比方說(shuō),調(diào)用View
的setVisibility()
方法設(shè)置其可見(jiàn)性兢交,或者Android
系統(tǒng)在重新創(chuàng)建時(shí)從bundle
恢復(fù)狀態(tài))薪捍。2.
Presenter
(或ViewModel
)有任意多個(gè)輸入(View
層觸發(fā)行為并交給Presenter
處理),這是ok的配喳,但同時(shí)Presenter
也有很多輸出(或MVP
中的輸出通道酪穿,如view.showLoading()
或view.showError()
;在MVVM
中,ViewModel
的實(shí)現(xiàn)中也提供了多個(gè)Observable
晴裹,這最終導(dǎo)致了View
層被济,Presenter
層和業(yè)務(wù)邏輯中狀態(tài)的沖突,在處理多線程的時(shí)候涧团,這種情況更明顯只磷。
在好的情況下,這只會(huì)導(dǎo)致視覺(jué)上的錯(cuò)誤泌绣,例如同時(shí)顯示加載指示符(“加載狀態(tài)”)和錯(cuò)誤指示符(“錯(cuò)誤狀態(tài)”)钮追,如下所示:
在最糟糕的情況下,您從崩潰報(bào)告工具(如Crashlytics
)接收到了一個(gè)嚴(yán)重的錯(cuò)誤報(bào)告阿迈,但您無(wú)法重現(xiàn)這個(gè)錯(cuò)誤元媚,因此也幾乎無(wú)從著手去修復(fù)它。
如果從 底層 (業(yè)務(wù)邏輯層)到 頂層 (UI視圖層)苗沧,有且僅有一個(gè)真實(shí)描述狀態(tài)的源刊棕,會(huì)怎么樣呢?事實(shí)上崎页,我們已經(jīng)在文章的開(kāi)頭談?wù)?code>Model的時(shí)候鞠绰,就已經(jīng)通過(guò)案例腰埂,把相似的概念展示了出來(lái):
class PersonsModel {
final boolean loading;
final List<Person> persons;
final Throwable error;
public(boolean loading, List<Person> persons, Throwable error){
this.loading = loading;
this.persons = persons;
this.error = error;
}
}
你猜怎么了飒焦? Model映射了狀態(tài),當(dāng)我想通了這點(diǎn)屿笼,許多狀態(tài)相關(guān)的問(wèn)題迎刃而解(甚至在編碼之前就已經(jīng)被避免了)牺荠;現(xiàn)在Presenter
層變得只有一個(gè)輸出了:
getView().render(PersonsModel)
它對(duì)應(yīng)了一個(gè)數(shù)學(xué)上簡(jiǎn)單的函數(shù),比如f(x) = y
,對(duì)于多個(gè)輸入的函數(shù)驴一,對(duì)應(yīng)的則是f(a,b,c)
,但也是一個(gè)輸出休雌。
并非對(duì)所有人來(lái)說(shuō)數(shù)學(xué)都是香茗,就好像數(shù)學(xué)家并不清楚bug是什么——但軟件工程師需要去品嘗它肝断。
了解Model
到底是什么以及如何建立對(duì)應(yīng)的Model
非常重要杈曲,因?yàn)樽罱KModel
可以解決 狀態(tài)問(wèn)題驰凛。
2.屏幕方向的改變
譯者注:針對(duì) 屏幕旋轉(zhuǎn)后的狀態(tài)回溯 這個(gè)問(wèn)題,已經(jīng)可以通過(guò)Google官方發(fā)布的
ViewModel
組件進(jìn)行處理担扑,開(kāi)發(fā)者不再需要為此煩惱恰响,但本章節(jié)仍值得一讀。
Android
設(shè)備上的 屏幕旋轉(zhuǎn) 是一個(gè)有足夠挑戰(zhàn)性的問(wèn)題;忽視它是一個(gè)最簡(jiǎn)單的解決方案涌献,即 每次屏幕旋轉(zhuǎn)胚宦,都對(duì)數(shù)據(jù)重新進(jìn)行加載 。這確實(shí)行之有效燕垃,大多數(shù)情況下枢劝,您的APP也在離線狀態(tài)下工作,其數(shù)據(jù)來(lái)源于數(shù)據(jù)庫(kù)或者其它本地緩存卜壕,這意味著屏幕旋轉(zhuǎn)后的數(shù)據(jù)加載速度是很快的您旁。
但是,個(gè)人而言我不喜歡看到加載框印叁,哪怕加載速度是毫秒級(jí)別的被冒,因?yàn)槲艺J(rèn)為這并非完美的用戶體驗(yàn),因此大家(包括我)開(kāi)始使用MVP
轮蜕,這其中包括了 保留性的Presenter——這樣就可以 在屏幕旋轉(zhuǎn)時(shí)分離和銷(xiāo)毀View層昨悼,而Presenter
則會(huì)保存在內(nèi)存中不會(huì)被銷(xiāo)毀,然后View
層會(huì)再次連接到Presenter
跃洛。
使用RxJava
的MVVM
也可以實(shí)現(xiàn)相同的概念率触,但請(qǐng)牢記,一旦View
對(duì)ViewModel
取消了訂閱汇竭,可觀察的流就會(huì)被銷(xiāo)毀葱蝗,這個(gè)問(wèn)題你可以用Subject
解決;對(duì)于DataBinding
構(gòu)建的MVVM
來(lái)講细燎,ViewModel
由DataBinding
直接綁定到View
層两曼,為了避免內(nèi)存泄露,需要我們?cè)谄聊恍D(zhuǎn)時(shí)及時(shí)銷(xiāo)毀ViewModel
玻驻。
對(duì)于 保留性的Presenter 或者 ViewModel 的問(wèn)題是: 我們?nèi)绾螌?code>View的狀態(tài)在屏幕旋轉(zhuǎn)之后回溯悼凑,保證View
和Presenter
再次回到之前相同的狀態(tài)?我編寫(xiě)了一個(gè)名為 Mosby 的MVP庫(kù)璧瞬,其包含一個(gè)名為ViewState
的功能户辫,它基本上將業(yè)務(wù)邏輯的狀態(tài)與View
同步。 Moxy,另一個(gè)MVP庫(kù)嗤锉,提出了一個(gè)非常有趣的解決方案——通過(guò)使用commands
在屏幕方向更改后重現(xiàn)View的狀態(tài):
針對(duì)View
層狀態(tài)的問(wèn)題,我很確定還有其他的解決方案渔欢。讓我們退后一步,歸納一下這些庫(kù)試圖解決的問(wèn)題:那就是我們已經(jīng)討論過(guò)的 狀態(tài)問(wèn)題瘟忱。
再次重申奥额,我們通過(guò)一個(gè) 能反映當(dāng)前狀態(tài)的Model 和一個(gè)渲染Model的方法 解決了這個(gè)問(wèn)題苫幢,就像調(diào)用getView().render(PersonsModel)
一樣簡(jiǎn)單。
3.在頁(yè)面堆棧中導(dǎo)航
當(dāng)View
不再使用時(shí)垫挨,是否還有保留Presenter
(或ViewModel
)的必要态坦?比如,用戶跳轉(zhuǎn)到了另外一個(gè)界面棒拂,這導(dǎo)致Fragment
(View
)被另外的Fragment
給replace
了伞梯,因此Presenter
已經(jīng)不在被任何View
持有。
如果沒(méi)有View
層和Presenter
進(jìn)行關(guān)聯(lián)帚屉,Presenter
自然也無(wú)法根據(jù)業(yè)務(wù)邏輯谜诫,將最新的數(shù)據(jù)反映在View
上。但如果用戶又回來(lái)了怎么辦(比如按下后退按鈕)攻旦,是 重新加載數(shù)據(jù) 還是 重用現(xiàn)有的Presenter?——這看起來(lái)像是一個(gè)哲學(xué)問(wèn)題喻旷。
通常用戶一旦回到之前的界面尖殃,他會(huì)期望回到之前的界面繼續(xù)操作耘成。這仍然像是第二小節(jié)關(guān)于View
層 狀態(tài)恢復(fù) 的問(wèn)題,解決方案簡(jiǎn)明扼要:當(dāng)用戶返回時(shí)给僵,我們得到 代表狀態(tài)的Model 烙无,然后只需調(diào)用 getView().render(PersonsModel)
對(duì)View
層進(jìn)行渲染锋谐。
4.進(jìn)程終止
進(jìn)程終止是一件壞事,并且我們需要依賴(lài)一些庫(kù)以幫助我們?cè)谶M(jìn)程終止后對(duì)狀態(tài)進(jìn)行恢復(fù)——我認(rèn)為這是Android
開(kāi)發(fā)中常見(jiàn)的一種誤解截酷。
首先涮拗,進(jìn)程終止的原因只有一個(gè),并且有足夠充分的理由——Android
操作系統(tǒng)需要更多資源用于其他應(yīng)用程序或節(jié)省電池迂苛。如果你的APP處于前臺(tái)并且正在被用戶主動(dòng)使用時(shí)三热,這種情況永遠(yuǎn)不會(huì)發(fā)生,因此三幻,遵紀(jì)守法就漾,不要與平臺(tái)作斗爭(zhēng)了(就是不要執(zhí)拗于所謂的進(jìn)程保活了)念搬。如果你真的需要在后臺(tái)進(jìn)行一些長(zhǎng)時(shí)間的工作抑堡,請(qǐng)使用Service
,這也是向操作系統(tǒng)發(fā)出信號(hào),告知您的App仍處于“主動(dòng)使用狀態(tài)”的 唯一方式 锁蠕。
如果進(jìn)程終止了夷野,Android
會(huì)提供一些回調(diào)以供 保存狀態(tài)懊蒸,比如onSaveInstanceState()
——沒(méi)錯(cuò)荣倾,又是 狀態(tài) 。我們應(yīng)該將View
的信息保存在Bundle
中嗎骑丸?我們是否也應(yīng)該把Presenter
中的狀態(tài)保存到Bundle
中舌仍?那么業(yè)務(wù)邏輯的狀態(tài)呢妒貌?又是老生常談的問(wèn)題,就和上面三個(gè)小節(jié)談到的一樣铸豁。
我們只需要一個(gè)代表整個(gè)狀態(tài)的Model
類(lèi)灌曙,我們很容易將Model
保存在Bundle
中并在之后對(duì)它進(jìn)行恢復(fù)。但是节芥,我個(gè)人認(rèn)為大部分情況下最好不保存狀態(tài)在刺,而是 重新加載整個(gè)界面,就像我們第一次啟動(dòng)App一樣头镊。 想想顯示新聞列表的 NewsReader App
蚣驼。 當(dāng)App被殺掉,我們保存了狀態(tài)相艇,6小時(shí)后用戶重新打開(kāi)App并恢復(fù)了狀態(tài)颖杏,我們的App可能會(huì)顯示過(guò)時(shí)的內(nèi)容。因此,這種情況下坛芽,也許不存儲(chǔ)Model
和狀態(tài)留储、而對(duì)數(shù)據(jù)重新加載才是更好的策略。
5.單向數(shù)據(jù)流的不變性
在這里我不打算討論不變性(immutabiliy
)的優(yōu)勢(shì)咙轩,因?yàn)橛泻芏噘Y源討論這個(gè)問(wèn)題获讳。我們想要一個(gè)不可變的Model
(代表狀態(tài))。為什么活喊?因?yàn)槲覀兿胍ㄒ坏臓顟B(tài)源赔嚎,在傳遞Model
時(shí),我們不希望App中的其他組件可以改變我們的Model
或者State
胧弛。
讓我們假設(shè)編寫(xiě)一個(gè)簡(jiǎn)單的計(jì)數(shù)器App尤误,它具有遞增和遞減的功能按鈕,并在TextView
中顯示當(dāng)前計(jì)數(shù)器值结缚。 如果我們的Model
(在這種情況下只是計(jì)數(shù)器值损晤,即一個(gè)整數(shù))是不可變的,那么我們?nèi)绾胃挠?jì)數(shù)器红竭?
我很高興被問(wèn)到這個(gè)問(wèn)題尤勋,按鈕被點(diǎn)擊時(shí),我們并非直接操作TextView
茵宪。我的建議是:
- 1.我們的
View
層應(yīng)該有一個(gè)類(lèi)似view.render(...)
的方法最冰; - 2.我們的
Model
是不可變的,因此不可直接修改Model
; - 3.
View
的渲染有且只有一個(gè)來(lái)源:即業(yè)務(wù)邏輯稀火。
我們將點(diǎn)擊事件 下沉 到業(yè)務(wù)邏輯層暖哨。業(yè)務(wù)邏輯知道當(dāng)前的Model
(例如,持有一個(gè)私有的成員Model
凰狞,它代表著當(dāng)前的狀態(tài)), 這之后根據(jù)舊的Model篇裁,創(chuàng)建一個(gè)新的帶有增量/減量值的Model
沛慢。
這樣我們建立了一個(gè) 單向數(shù)據(jù)流,業(yè)務(wù)邏輯作為單一源用于創(chuàng)建不可變的Model
實(shí)例达布,但對(duì)于一個(gè)計(jì)數(shù)器來(lái)講未免有點(diǎn)小題大做团甲,不是嗎?誠(chéng)然黍聂,是的躺苦,計(jì)數(shù)器只是一個(gè)簡(jiǎn)單的應(yīng)用程序。大多數(shù)應(yīng)用程序都是以簡(jiǎn)單的應(yīng)用程序開(kāi)始产还,但復(fù)雜性增長(zhǎng)很快——從我的角度來(lái)看圾另,單向數(shù)據(jù)流和不可變模型是必要的,這會(huì)使簡(jiǎn)單的應(yīng)用程序雕沉,在復(fù)雜性遞增的同時(shí)集乔,依然保持著簡(jiǎn)單(對(duì)開(kāi)發(fā)者而言)。
6.可調(diào)試和可重現(xiàn)的狀態(tài)
此外坡椒,單向數(shù)據(jù)流保證了我們的應(yīng)用程序易于調(diào)試扰路。下次我們從Crashlytics獲得崩潰報(bào)告時(shí),我們可以輕松地重現(xiàn)并修復(fù)此崩潰倔叼,因?yàn)樗斜匦璧男畔⒍家迅郊拥奖罎?bào)告中了汗唱。
什么叫做必需的信息?那就是當(dāng)前的Model
和用戶用戶在崩潰發(fā)生時(shí)想要執(zhí)行的操作(比如丈攒,點(diǎn)擊減量按鈕)哩罪。這就是我們重現(xiàn)這次崩潰所需的全部信息,這些信息非常容易收集并附加在崩潰報(bào)告中巡验。
如果沒(méi)有單項(xiàng)數(shù)據(jù)流(比如际插,對(duì)EventBus
的濫用,或者將CounterModels
的私有域暴露出來(lái))显设,或者沒(méi)有不變性(這會(huì)導(dǎo)致我們不知道誰(shuí)實(shí)際更改了Model
)框弛,那么bug的復(fù)現(xiàn)就沒(méi)那么容易了。
7.可測(cè)試性
“傳統(tǒng)”的MVP
或MVVM
提高了應(yīng)用程序的可測(cè)試性捕捂。MVC
也是可測(cè)試的:沒(méi)有人說(shuō)我們必須將所有業(yè)務(wù)邏輯放入Activity
中瑟枫。使用表示狀態(tài)的Model
,我們可以簡(jiǎn)化單元測(cè)試的代碼慷妙,因?yàn)槲覀兛梢院?jiǎn)單地檢查assertEqual(expectedModel,model)
膝擂。這使我們避免了許多必須要Mock
的對(duì)象。
此外猿挚,這也減少了很多驗(yàn)證的測(cè)試驶鹉,即某些方法是否被調(diào)用(比如Mockito.verify(view, times(1)).showFoo()
),最終室埋,這使得我們的單元測(cè)試代碼更具可讀性,易于理解并且易于維護(hù)姚淆,因?yàn)槲覀儾槐靥幚砗芏鄬?shí)際代碼的實(shí)現(xiàn)細(xì)節(jié)。
總結(jié)
在這個(gè)博客文章系列的第一部分中腌逢,我們談了很多關(guān)于理論的東西。我們真的需要關(guān)于專(zhuān)門(mén)討論Model
的博客嗎搏讶?
我認(rèn)為初步地理解Model
的確很重要,這也有助于我們避免一些會(huì)遇到的問(wèn)題媒惕。Model
并不意味著業(yè)務(wù)邏輯,它是生成Model
的業(yè)務(wù)邏輯(比如妒蔚,一次交互,一個(gè)用例肴盏,一個(gè)倉(cāng)庫(kù)或者你在APP中調(diào)用的任何東西)科盛。
在接下來(lái)的第二部分中,當(dāng)我們最終使用Model-View-Intent
構(gòu)建一個(gè)響應(yīng)式App 時(shí)菜皂,我們將看到Model
的實(shí)際應(yīng)用土涝。演示的APP是一個(gè)虛構(gòu)的在線商店的應(yīng)用程序,敬請(qǐng)關(guān)注幌墓。
系列目錄
《使用MVI打造響應(yīng)式APP》原文
《使用MVI打造響應(yīng)式APP》譯文
- [譯]使用MVI打造響應(yīng)式APP(一):Model到底是什么
- [譯]使用MVI打造響應(yīng)式APP[二]:View層和Intent層
- [譯]使用MVI打造響應(yīng)式APP[三]:狀態(tài)折疊器
- [譯]使用MVI打造響應(yīng)式APP[四]:獨(dú)立性UI組件
- [譯]使用MVI打造響應(yīng)式APP[五]:輕而易舉地Debug
- [譯]使用MVI打造響應(yīng)式APP[六]:恢復(fù)狀態(tài)
- [譯]使用MVI打造響應(yīng)式APP[七]:掌握時(shí)機(jī)(SingleLiveEvent問(wèn)題)
- [譯]使用MVI打造響應(yīng)式APP[八]:導(dǎo)航
《使用MVI打造響應(yīng)式APP》實(shí)戰(zhàn)
關(guān)于我
Hello但壮,我是卻把清梅嗅,如果您覺(jué)得文章對(duì)您有價(jià)值常侣,歡迎 ??蜡饵,也歡迎關(guān)注我的博客或者Github。
如果您覺(jué)得文章還差了那么點(diǎn)東西胳施,也請(qǐng)通過(guò)關(guān)注督促我寫(xiě)出更好的文章——萬(wàn)一哪天我進(jìn)步了呢溯祸?