從零開始的Android新項(xiàng)目3 - MVPVM in Action, 誰(shuí)告訴你MVP和MVVM是互斥的

前言

去年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

MVC: View持有Controller,傳遞事件給Controller,Controller由此去觸發(fā)Model層事件,Model更新完數(shù)據(jù)(如從網(wǎng)絡(luò)或者數(shù)據(jù)庫(kù)獲得數(shù)據(jù)后)觸發(fā)View的更新事件。

MVP

乍一看,MVP似乎是MVC的變種,即C的位置被P取代了,但如果我們?cè)倏匆豢聪聢D:

MVCP

其實(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

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:

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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,510評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,866評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,036評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,331評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,536評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,754評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,273評(píng)論 3 399
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,505評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容