Android App的設計架構:MVC, MVP, MVVM

目前已轉至個人博客,本系列地址:Lam's Blog - Knowledge as Action

前言

本文基于周鴻博的文章Android App的設計架構:MVC,MVP,MVVM與架構經驗談,同時結合網上其余與MVC,MVP,MVVM相關的文章。

關于重構的一些話

該文章可以作為項目初期架構選型時的參考,也可作為項目開展一些階段之后進行重構的參考,所以首先扯點題外話:為什么我們需要重構?

  • 重構改進軟件設計
    只為了短期目的或者在完全理解整體設計之前編寫出來的代碼,會導致程序逐漸失去自己的結構。這時如果沒有重構,程序的設計會逐漸腐敗變質,程序員愈來愈難通過閱讀源碼而理解原本設計。重構很像是在整理代碼,你所做的就是讓所有東西回到應該的位置上。代碼結構的流失是累積性的。愈難看出代碼所代表的設計意涵,就愈難保護其中設計,于是該設計就腐敗得愈快。經常性的重構可以幫助代碼維持自己該有的形態。
  • 重構使軟件更容易理解
    所謂程序設計,很大程度上就是與計算機交談:你編寫代碼告訴計算機做什么事,它的響應則是精確按照你的指示行動。你得及時填補“想要它做什么”和“告訴它做什么”之間的縫隙。這種編程模式的核心就是“準確說出我所要的”。除了計算機外,你的源碼還有其他讀者:幾個月之后可能會有另一位程序員嘗試讀懂你的代碼并做一些修改。我們很容易忘記這第二位讀者,但他才是最重要的。計算機是否多花了幾個小時來編譯,又有什么關系呢?如果一個程序員花費一周時間來修改某段代碼,那才關系重大—如果他理解你的代碼,這個修改原本只需一小時。
  • 重構幫助找到bug
    對代碼的理解,可以幫助我們找到bug。有些人只要盯著一大段代碼就可以找出里面的bug,但大多數人不行。如果對代碼進行重構,我們就可以深入理解代碼的作為,并恰到好處地把新的理解反饋回去。搞清楚程序結構的同時,也清楚了自己所做的一些假設,從而更加容易把bug揪出來。
  • 重構提高編程速度
    良好的設計是快速開發的根本──事實上,擁有良好設計才可能做到快速開發。如果沒有良好設計,或許某一段時間內你的進展迅速,但惡劣的設計很快就讓你的速度慢下來。你會把時間花在調試上面,難以或者甚至無法添加新功能,修改時間愈來愈長,擴展性與可維護性越來越差。因為你必須花愈來愈多的時間去理解系統、尋找重復代碼。隨著你給最初程序打上一個又一個的補丁,新特性需要更多代碼才能實現,最終變成一個惡性循環。
  • 重構使單元測試成為可能
    Android作為對單元測試最不友好的系統環境,大多數開發者在剛開始接觸時都會直觀地把和當前界面有關的所有邏輯都放在Activity或者Fragment等視圖里面,造成View層與邏輯層甚至數據層深度耦合,而單元測試對View的測試非常乏力,也不值得花大量時間在這上面,同時因為邏輯的過度耦合,每一個類里面有非常多的私有依賴無法進行mock,從而無法達到盡可能全面測試的目的。但是單元測試可以測出整個測試流程中80%的bug,這是對代碼質量的一個重大保障。
  • 重構應該是開發人員的必備技術手段
    一名開發人員的良好發展路線應該是由會寫代碼轉為寫好代碼,也是從一名實現者到架構者的轉變,甚至即使是單純的實現者,也應該追求自己能寫出越來越優雅和高效的代碼。業務需求與技術需求不應該是對立面,技術需求最終也是服務于業務需求,更好的代碼結構才能幫助項目更快速地開發。

框架介紹與選擇

MVC

MVC

MVC簡介

MVC全名是Model View Controller,如圖,是模型(model)-視圖(view)-控制器(controller)的縮寫,一種軟件設計典范,用一種業務邏輯、數據、界面顯示分離的方法組織代碼,在改進和個性化定制界面及用戶交互的同時,不需要重新編寫業務邏輯。
其中M層處理數據,業務邏輯等;V層處理界面的顯示結果;C層起到橋梁的作用,來控制V層和M層通信以此來達到分離視圖顯示和業務邏輯層。

Android中的MVC

Android中界面部分也采用了當前比較流行的MVC框架,在Android中

  • 視圖層(View):
    一般采用XML文件進行界面的描述,這些XML可以理解為Android App的View。使用的時候可以非常方便的引入。同時便于后期界面的修改。邏輯中與界面對應的id不變化則代碼不用修改,大大增強了代碼的可維護性 *
  • 控制層(Controller):
    Android的控制層的重任通常落在了眾多的Activity的肩上。這句話也就暗含了不要在Activity中寫邏輯代碼,要通過Activity交割給Model業務邏輯層處理,這樣做的另外一個原因是Android中的Actiivity的響應時間是5s,如果耗時的操作放在這里,程序就很容易被回收掉。
  • 模型層(Model)
    我們針對業務模型,建立的數據結構和相關的類,就可以理解為AndroidApp的Model,Model是與View無關,而與業務相關的。對數據庫的操作、對網絡等的操作都應該在Model里面處理,當然對業務計算等操作也是必須放在的該層的。
  • 存在的問題
    xml作為view層,控制能力實在太弱了,你想去動態的改變一個頁面的背景,或者動態的隱藏/顯示一個按鈕,這些都沒辦法在xml中做,只能把代碼寫在activity中,造成了activity既是controller層,又是view層的這樣一個窘境。在Android開發中,Activity并不是一個標準的MVC模式中的Controller,它的首要職責是加載應用的布局和初始化用戶界面,并接受和處理來自用戶的操作請求,進而作出響應。隨著界面及其邏輯的復雜度不斷提升,Activity類的職責不斷增加,以致變得龐大臃腫。
    Android上的MVC模式,view層和model層是相互可知的,這意味著兩層之間存在耦合,耦合對于一個大型程序來說是非常致命的,因為這表示開發,測試,維護都需要花大量的精力。

MVP

MVP簡介

MVP從更早的MVC框架演變過來,與MVC有一定的相似性:Controller/Presenter負責邏輯的處理,Model提供數據,View負責顯示。
MVP框架由3部分組成:View負責顯示,Presenter負責邏輯處理,Model提供數據。在MVP模式里通常包含3個要素(加上View interface是4個):

  • view:負責繪制UI元素、與用戶進行交互(在Android中體現為Activity等)
  • Model:負責存儲、檢索、操縱數據(有時也實現一個Model interface用來降低耦合)
  • Presenter:作為View與Model交互的中間紐帶,處理與用戶交互的業務邏輯。
  • *View interface:需要View實現的接口,View通過View interface與Presenter進行交互,降低耦合,方便進行單元測試
  • Tips:*View interface的必要性
    回想一下你在開發Android應用時是如何對代碼邏輯進行單元測試的?是否每次都要將應用部署到Android模擬器或真機上,然后通過模擬用戶操作進行測試?然而由于Android平臺的特性,每次部署都耗費了大量的時間,這直接導致開發效率的降低。而在MVP模式中,處理復雜邏輯的Presenter是通過interface與View(Activity)進行交互的,這說明我們可以通過自定義類實現這個interface來模擬Activity的行為對Presenter進行單元測試,省去了大量的部署及測試的時間。

MVP的優點

  • 在MVP中,Activity的代碼不臃腫;
  • Model與視圖完全分離,我們可以修改視圖而不影響Model
  • 可以更高效地使用Model,因為所有的交互都發生在一個地方——Presenter內部
  • 我們可以將一個Presenter用于多個視圖,而不需要改變Presenter的邏輯。這個特性非常的有用,因為視圖的變化總是比Model的變化頻繁
  • 如果我們把邏輯放在Presenter中,那么我們就可以脫離用戶接口來測試這些邏輯(單元測試)
  • 具體到Android App中,一般可以將App根據程序的結構進行縱向劃分,根據MVP可以將App分別為模型層(M),UI層(V)和邏輯層(P)。
  • UI層一般包括Activity,Fragment,Adapter等直接和UI相關的類,UI層的Activity在啟動之后實例化相應的Presenter,App的控制權后移,由UI轉移到Presenter,兩者之間的通信通過BroadCast、Handler或者接口完成,只傳遞事件和結果。
  • 舉個簡單的例子,UI層通知邏輯層(Presenter)用戶點擊了一個Button,邏輯層(Presenter)自己決定應該用什么行為進行響應,該找哪個模型(Model)去做這件事,最后邏輯層(Presenter)將完成的結果更新到UI層。

MVP模式的一些弊端:

  • Presenter(以下簡稱P)層與View(以下簡稱V)層是通過接口進行交互的,接口粒度不好控制。粒度太小,就會存在大量接口的情況,使代碼太過碎版化;粒度太大,解耦效果不好。同時對于UI的輸入和數據的變化,需要手動調用V層或者P層相關的接口,相對來說缺乏自動性、監聽性。如果數據的變化能自動響應到UI、UI的輸入能自動更新到數據,那該多好!
  • MVP是以UI為驅動的模型,更新UI都需要保證能獲取到控件的引用,同時更新UI的時候要考慮當前是否是UI線程,也要考慮Activity的生命周期(是否已經銷毀等)。
  • MVP是以UI和事件為驅動的傳統模型,數據都是被動地通過UI控件做展示,但是由于數據的時變性,我們更希望數據能轉被動為主動,希望數據能更有活性,由數據來驅動UI。
  • V層與P層還是有一定的耦合度。一旦V層某個UI元素更改,那么對應的接口就必須得改,數據如何映射到UI上、事件監聽接口這些都需要轉變,牽一發而動全身。如果這一層也能解耦就更好了。
  • 復雜的業務同時也可能會導致P層太大,代碼臃腫的問題依然不能解決。

MVP的變種:Passive View

MVP的變種有很多,其中使用最廣泛的是Passive View模式,即被動視圖。在這種模式下,View和Model之間不能直接交互,View通過Presenter與Model打交道。Presenter接受View的UI請求,完成簡單的UI處理邏輯,并調用Model進行業務處理,并調用View將相應的結果反映出來。View直接依賴Presenter,但是Presenter間接依賴View,它直接依賴的是View實現的接口,相對于View的被動,那Presenter就是主動的一方。對于Presenter的主動,有如下的理解:

  • Presenter是整個MVP體系的控制中心,而不是單純的處理View請求的人;
  • View僅僅是用戶交互請求的匯報者,對于響應用戶交互相關的邏輯和流程,View不參與決策,真正的決策者是Presenter;
  • View向Presenter發送用戶交互請求應該采用這樣的口吻:“我現在將用戶交互請求發送給你,你看著辦,需要我的時候我會協助你”,不應該是這樣:“我現在處理用戶交互請求了,我知道該怎么辦,但是我需要你的支持,因為實現業務邏輯的Model只信任你”;
  • 對于綁定到View上的數據,不應該是View從Presenter上“拉”回來的,應該是Presenter主動“推”給View的;
  • View盡可能不維護數據狀態,因為其本身僅僅實現單純的、獨立的UI操作;Presenter才是整個體系的協調者,它根據處理用于交互的邏輯給View和Model安排工作。

MVP實踐

Google官方MVPDemo

MVC → MVP

MVC → MVP

當我們將Activity復雜的邏輯處理移至另外的一個類(Presenter)中時,Activity其實就是MVP模式中的View,它負責UI元素的初始化,建立UI元素與Presenter的關聯(Listener之類),同時自己也會處理一些簡單的邏輯(復雜的邏輯交由 Presenter處理)。
MVP的Presenter是框架的控制者,承擔了大量的邏輯操作,而MVC的Controller更多時候承擔一種轉發的作用。因此在App中引入MVP的原因,是為了將此前在Activty中包含的大量邏輯操作放到控制層中,避免Activity的臃腫。

兩種模式的區別:

  • (最主要區別)View與Model并不直接交互,而是通過與Presenter交互來與Model間接交互,從而實現了這兩者之間完全的解耦。而在MVC中View可以與Model直接交互
  • 通常View與Presenter是一對一的,但復雜的View可能綁定多個Presenter來處理邏輯。而Controller是基于行為的,并且可以被多個View共享,Controller可以負責決定顯示哪個View
  • Presenter與View的交互是通過接口來進行的,更有利于添加單元測試

MVVM

MVVM最早應用于Windows的WPF中,在安卓上使用該框架需要對谷歌的data-binding框架有一定的了解才能熟練使用。MVVM可以算是MVP的升級版,其中的VM是ViewModel的縮寫,ViewModel可以理解成是View的數據模型和Presenter的合體,ViewModel和View之間的交互通過Data Binding完成,而Data Binding可以實現雙向的交互,這就使得視圖和控制層之間的耦合程度進一步降低,關注點分離更為徹底,同時減輕了Activity的壓力。但是由于去除了Presenter層,會導致view層依然過重,所以衍生了其他類似于MVPVM框架。
剛開始理解這些概念的時候認為這幾種模式雖然都是要將view和model解耦,但是非此即彼,沒有關系,一個應用應該只會用一種模式。后來發現這幾種模式的邊界并非那么明顯,可能你在自己的應用中都會用到。實際上也根本沒必要去糾結自己到底用的是MVC、MVP還是MVVP,不管黑貓白貓,捉住老鼠就是好貓。


MVPVM

MVC → MVP → MVVM 的演進過程

MVC → MVP → MVVM

MVC → MVP → MVVM 這幾個軟件設計模式是一步步演化發展的,MVVM 是從 MVP 的進一步發展與規范,MVP 隔離了MVC中的 M 與 V 的直接聯系后,靠 Presenter 來中轉,所以使用 MVP 時 P 是直接調用 View 的接口來實現對視圖的操作的,這個 View 接口的東西一般來說是 showData、showLoading等等。
M 與 V 已經隔離了,方便測試了,但代碼還不夠優雅簡潔,所以 MVVM 就彌補了這些缺陷。在 MVVM 中就出現的 Data Binding 這個概念,意思就是 View 接口的 showData 這些實現方法可以不寫了,通過 Binding 來實現。

  • 同 → 如果把這三者放在一起比較,先說一下三者的共同點,也就是Model和View:
    Model:數據對象,同時,提供本應用外部對應用程序數據的操作的接口,也可能在數據變化時發出變更通知。Model不依賴于View的實現,只要外部程序調用Model的接口就能夠實現對數據的增刪改查。
    View:UI層,提供對最終用戶的交互操作功能,包括UI展現代碼及一些相關的界面邏輯代碼。
  • 異 → 三者的差異在于如何粘合View和Model,實現用戶的交互操作以及變更通知
    Controller
    Controller接收View的操作事件,根據事件不同,或者調用Model的接口進行數據操作,或者進行View的跳轉,從而也意味著一個Controller可以對應多個View。Controller對View的實現不太關心,只會被動地接收,Model的數據變更不通過Controller直接通知View,通常View采用觀察者模式監聽Model的變化。
    Presenter
    Presenter與Controller一樣,接收View的命令,對Model進行操作;與Controller不同的是Presenter會反作用于View,Model的變更通知首先被Presenter獲得,然后Presenter再去更新View。一個Presenter只對應于一個View。根據Presenter和View對邏輯代碼分擔的程度不同,這種模式又有兩種情況:Passive View和Supervisor Controller。
    ViewModel
    注意這里的“Model”指的是View的Model,跟MVVM中的一個Model不是一回事。所謂View的Model就是包含View的一些數據屬性和操作的類,這種模式的關鍵技術就是數據綁定(data binding),View的變化會直接影響ViewModel,ViewModel的變化或者內容也會直接體現在View上。這種模式實際上是框架替應用開發者做了一些工作,開發者只需要較少的代碼就能實現比較復雜的交互。

一點總結

  • MVP和MVVM完全隔離了Model和View,但是在有些情況下,數據從Model到ViewModel或者Presenter的拷貝開銷很大,可能也會結合MVC的方式,Model直接通知View進行變更,甚至還有衍生出MVPVM的架構模式。在實際的應用中很有可能你已經在不知不覺中將幾種模式融合在一起,但是為了代碼的可擴展、可測試性,必須做到模塊的解耦,不相關的代碼不要放在一起。其實只要一個框架提供了視圖和模型分離的功能,我們的目的就達到了。在開發深入之后,可以再體會用到的框架到底是MVC、MVP還是MVVM。
  • 因為做了視圖與邏輯和數據模型的抽離,所以必然在原有代碼的基礎上增加很多類,但是整個代碼的結構卻反而會更加清晰,項目也更易于維護和擴展
  • 最佳實踐:MVP+data-binding,既使用了data binding框架去節省了類似findViewById和數據綁定的時間,又使用了presenter去將業務邏輯和view層分離

參考文章

Android App的設計架構:MVC,MVP,MVVM與架構經驗談
淺談Android架構之MVP,MVVM
認清Android框架 MVC,MVP和MVVM
如何構建Android MVVM 應用框架

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容