前言
去年5月左右的時(shí)候,筆者在逛GitHub的時(shí)候,看到了一個(gè)MVP的項(xiàng)目,叫做mosby,仔細(xì)看了源碼和作者介紹的文章后,發(fā)現(xiàn)確實(shí)有點(diǎn)意思,雖然會(huì)需要多寫幾個(gè)類和方法,但是解決了activity/fragment過(guò)重的問(wèn)題,通過(guò)V/P分離能夠幫助提高可維護(hù)性。時(shí)至去年年底,今年年初,MVP才逐漸被大家所知,也不時(shí)看到些文章介紹其概念和實(shí)踐。
再說(shuō)說(shuō)MVVM (Model-View-ViewModel),在Android上對(duì)應(yīng)data binding。即ViewModel到View的映射,不需要再去自己找到view,然后更新字段,而是在映射建立后直接更新ViewModel然后反映到View上。
值得一提的是,MVP和MVVM都是微軟提出的理念,最早都是在WPF里面被應(yīng)用的,只是時(shí)至今日才在Android上被真正用起來(lái)。本文不是來(lái)介紹這兩個(gè)的,所以不再贅述,講講正題。
在本系列首篇中我曾經(jīng)提到過(guò)在我設(shè)計(jì)的新應(yīng)用中,采用了MVP+MVVM的混合(當(dāng)初也考慮過(guò)Flux,但感覺(jué)并不合適Android),后來(lái)有一次CJJ同學(xué)和我探討這個(gè)架構(gòu)的時(shí)候,問(wèn)到了我有沒(méi)有什么正式的名字,我就有楞,因?yàn)檫@個(gè)組合和應(yīng)用是我自己設(shè)計(jì)的,所以還真沒(méi)想過(guò)這個(gè)問(wèn)題,Google一搜,還真有這么個(gè)東西(見參考資料,文章寫得很棒,建議英文不錯(cuò)的同學(xué)讀一讀)!
這就是本文我要介紹的東西,MVPVM (Model-View-Presenter-ViewModel)。
Quick glance
以下所有Model,并不單單指的是Bean,而是Model層,更像是repository或者business logic。
MVC: View持有Controller,傳遞事件給Controller,Controller由此去觸發(fā)Model層事件,Model更新完數(shù)據(jù)(如從網(wǎng)絡(luò)或者數(shù)據(jù)庫(kù)獲得數(shù)據(jù)后)觸發(fā)View的更新事件。
乍一看,MVP似乎是MVC的變種,即C的位置被P取代了,但如果我們?cè)倏匆豢聪聢D:
其實(shí)MVP是MVC的一個(gè)wrap,C層仍然可以在那里,代替View處理點(diǎn)擊事件、數(shù)據(jù)綁定、扮演ListView的觀察者,從而View可以專注于處理純視覺(jué)的一些東西。而Presenter則避免了Model直接去觸發(fā)View的更新,View徹底成為了一個(gè)被動(dòng)的東西,只有Presenter告知其更新視覺(jué),它才會(huì)去更新,比如showLoading(), showEmpty()。
MVVM通過(guò)View和ViewModel的雙向綁定,讓我們可以
- 直接更新ViewModel,View會(huì)進(jìn)行對(duì)應(yīng)刷新
- View的事件直接傳遞到ViewModel,ViewModel去對(duì)Model進(jìn)行操作并接受更新。
Why MVPVM
如果你仔細(xì)讀過(guò)Clean architecture的源碼,會(huì)發(fā)現(xiàn)其中已經(jīng)有了ViewModel這一層。如果你熟悉DO(Domain Object),PO(Persistent Object),VO(View Object),或許了解visibility這個(gè)概念,各層只需要知道其應(yīng)該知道的。這些Object代表了完全獨(dú)立不同的概念。
ViewModel層的必要性,簡(jiǎn)單舉個(gè)例子,服務(wù)器傳來(lái)一個(gè)Date String,但我們需要顯示的是該Date到現(xiàn)在的相對(duì)時(shí)間描述,比如1分鐘前,2天前,為了避免在view中綁定數(shù)據(jù)時(shí)去做這個(gè)邏輯,ViewModel會(huì)代替來(lái)進(jìn)行這個(gè)的轉(zhuǎn)換。
MVVM盡管確實(shí)省去了綁定數(shù)據(jù)到View的boilerplate,但
- ViewModel引用了View,從而導(dǎo)致ViewModel無(wú)法重用于其他View。
- 并沒(méi)有解決View層過(guò)重的問(wèn)題,僅僅去掉了數(shù)據(jù)綁定,尤其對(duì)一些復(fù)雜業(yè)務(wù)邏輯的頁(yè)面。
模式的引入都是為了通過(guò)可拔插化以提高可復(fù)用性,松耦合和盡量小的接口可以給予最大的可復(fù)用性,使得組件能重組使用。
所以有了MVPVM:
在我的個(gè)人實(shí)踐中:
- Model: data和domain模塊組成,包含了Interactor(UseCase)、Repository、Datastore、Retrofit、Realm、DO、部分PO等。
- View: Activity/Fragment。
- Presenter:Presenter,包含了Subscriber,并通過(guò)Dagger2注入U(xiǎn)seCase從而減輕耦合。
- ViewModel:由Model轉(zhuǎn)換而成,繼承BaseObservable或SortedList,大部分直接wrap了model,從而去掉了mapper的boilerplate。通過(guò)Data Binding綁定到xml。
從Presenter的Subscriber往下都是RxJava的流世界,stream in stream out。如果你原來(lái)就應(yīng)用了MVP或者Clean Architecture,那會(huì)發(fā)現(xiàn)再加上ViewModel簡(jiǎn)直太簡(jiǎn)單了,同時(shí)讓代碼庫(kù)更小,邏輯更清晰。
接著看看各個(gè)組件在MVPVM中的standing。
MVPVM: Model
實(shí)際對(duì)應(yīng)的是Repository層,即第一篇文章中提到的data/domain module。具體的Model理論上應(yīng)該是PO,但我們大部分場(chǎng)景并不需要PO,所以也可以是domain層的DO。
MVPVM: View
View對(duì)ViewModel不需要了解太多,這樣才能保持兩者的解耦,兩者之間的協(xié)議只需要:
- ViewModel支持View需要展示的properties。
- View實(shí)現(xiàn)了ViewModel的觀察者模式接口(如Listener)。
所以這里ViewModel到View是一條虛線,而不是MVVM中的雙向?qū)嵕€。
MVPVM: Presenter
和在MVP一樣,Presenter站在View和Model層之間。這里值得一提的是Presenter到ViewModel是有耦合的,因?yàn)镻resenter需要把model更新到ViewModel中,也就是map行為,然后調(diào)用View的對(duì)應(yīng)接口進(jìn)行binding。
Presenter是MVPVM中唯一不需要解耦的,它緊緊地與View、ViewModel、Model層耦合。如果你的Presenter被多個(gè)View重用了,那你可能需要考慮它是不是更應(yīng)該作為一個(gè)module,比如(第三方)登陸。
MVPVM: ViewModel
MVPVM讓ViewModel可以重用,因?yàn)樗僖膊皇侵苯雍吞囟╒iew綁定,而僅僅作為數(shù)據(jù)到View的一個(gè)綁定用展示。ViewModel因?yàn)橛脩舨僮鞫|發(fā)的事件不再直接對(duì)Model進(jìn)行操作,而由View去負(fù)責(zé)任務(wù)流。ViewModel本身基本沒(méi)有field,而是通過(guò)暴露get方法來(lái)讓data binding找到對(duì)應(yīng)要顯示的property,get方法中直接調(diào)用持有的model的對(duì)應(yīng)屬性get方法。
理想化的架構(gòu)是通過(guò)一個(gè)mapper類進(jìn)行轉(zhuǎn)換,但我想大部分的程序員面對(duì)這個(gè)工作都會(huì)抓狂,畢竟很多字段其實(shí)就是一個(gè)復(fù)制,而且對(duì)性能也有一些影響(遍歷list,new對(duì)象,一個(gè)個(gè)字段轉(zhuǎn)換,添加到新的list)。所以折中地,讓ViewModel持有Model,在get方法中直接返回對(duì)應(yīng)model的具體字段,在一些特殊的field如相對(duì)時(shí)間、添加一些描述性字符的地方再去進(jìn)行拼接和特殊處理。
啊,對(duì)了,說(shuō)到ViewModel,Data Binding現(xiàn)在支持雙向綁定了哦,見https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android/,語(yǔ)法如:
<EditText android:text="@={user.firstName}" .../>
不同于單向綁定的@{},使用了@={},畢竟雙向綁定這個(gè)東西還是慎用,一方面早成數(shù)據(jù)流混亂不好理解,另一方面容易出現(xiàn)死循環(huán)。
NO Presenter
在MVP中,我們有時(shí)候碰到的問(wèn)題是,Presenter真的有必要存在嗎,尤其是一些較為靜態(tài),沒(méi)什么業(yè)務(wù)邏輯,只需要純展示的頁(yè)面,結(jié)果就是為了MVP而特意去創(chuàng)建一個(gè)Presenter。
所以Presenter不應(yīng)該被強(qiáng)求,正如MVP中,V和C其實(shí)被并在了一起,在某些情況下(確實(shí)就是個(gè)純展示,或者很少的業(yè)務(wù)邏輯),應(yīng)該允許去Presenter,并讓View承擔(dān)其任務(wù)。比如注冊(cè)頁(yè)面,我真的就只是想把用戶的輸入發(fā)到服務(wù)器驗(yàn)證一下,何必非得去搞一個(gè)presenter套著呢?
我們不能永遠(yuǎn)理想化地去選擇所謂最好的設(shè)計(jì),在現(xiàn)實(shí)的必要情況下,我們要敢于舍棄,最合適的設(shè)計(jì)才是最好的設(shè)計(jì)。為此,Presenter不是強(qiáng)制的;為此,ViewModel并不一定通過(guò)mapper生成,而可以返回持有的DO對(duì)象對(duì)應(yīng)字段。
總結(jié)
本篇講了講MVPVM及其在Android的實(shí)踐,因?yàn)闀r(shí)間原因來(lái)不及寫個(gè)demo來(lái)說(shuō)說(shuō)具體實(shí)現(xiàn),歡迎大家提出意見和建議。有空的話我最近會(huì)在GitHub上寫一下demo,你如果有興趣可以follow一下等等更新: markzhai。
下集預(yù)告
Dagger匕首,比ButterKnife黃油刀鋒利得多。Square為什么這么有自信地給它取了這個(gè)名字,Google又為什么會(huì)拿去做了Dagger2呢?筆者看了很多國(guó)內(nèi)Dagger2的文章,但發(fā)現(xiàn)它們都保留在介紹API和官網(wǎng)翻譯的層面,無(wú)法讓讀者能明白究竟為什么用Dagger2,又如何用好Dagger2。希望能在下一次為大家講清楚。
參考資料
原文:http://blog.zhaiyifan.cn/2016/03/14/android-new-project-from-0-p3/