Android App的三種架構模式MVC,MVP和MVVM
http://www.2cto.com/kf/201506/405766.html
MVC使用總結
利用MVC設計模式,使得這個天氣預報小專案有了很好的可擴充套件和維護性,當需要改變UI顯示的時候,無需修改Contronller(控制器)Activity的程式碼和Model(模型)WeatherModel模型中的業務邏輯程式碼,很好的將業務邏輯和介面顯示分離。
在Android專案中,業務邏輯,資料處理等擔任了Model(模型)角色,XML介面顯示等擔任了View(檢視)角色,Activity擔任了Contronller(控制器)角色。contronller(控制器)是一箇中間橋樑的作用,通過介面通訊來協同 View(檢視)和Model(模型)工作,起到了兩者之間的通訊作用。
什麼時候適合使用MVC設計模式?當然一個小的專案且無需頻繁修改需求就不用MVC框架來設計了,那樣反而覺得程式碼過度設計,程式碼臃腫。一般在大的專案中,且業務邏輯處理複雜,頁面顯示比較多,需要模組化設計的專案使用MVC就有足夠的優勢了。
4.在MVC模式中我們發現,其實控制器Activity主要是起到解耦作用,將View檢視和Model模型分離,雖然Activity起到互動作用,但是找Activity中有很多關於檢視UI的顯示程式碼,因此View檢視和Activity控制器並不是完全分離的,也就是說一部分View檢視和Contronller控制器Activity是繫結在一個類中的。
MVC的優點:
(1)耦合性低。所謂耦合性就是模組程式碼之間的關聯程度。利用MVC框架使得View(檢視)層和Model(模型)層可以很好的分離,這樣就達到了解耦的目的,所以耦合性低,減少模組程式碼之間的相互影響。
(2)可擴充套件性好。由於耦合性低,新增需求,擴充套件程式碼就可以減少修改之前的程式碼,降低bug的出現率。
(3)模組職責劃分明確。主要劃分層M,V,C三個模組,利於程式碼的維護。
------------------------------------------------------------------------------------------
相信大家對MVC,MVP和MVVM都不陌生,作為三個最耳熟能詳的Android框架,它們的應用可以是非常廣泛的,但是對於一些新手來說,可能對於區分它們三個都有困難,更別說在實際的專案中應用了,有些時候想用MVP的,程式碼寫著寫著就變成了MVC,久而久之就對它們三個的選擇產生了恐懼感,如果你也是這樣的人群,那麼這篇文章可能會對你有很大的幫助,希望大家看完都會有收穫吧!
文章重點:
(1)瞭解並區分MVC,MVP,MVVM。
(2)知道這三種模式在Android中如何使用。
(3)走出data binding的誤區。
(4)瞭解MVP+data binding的開發模式。
本篇文章的demo我將會上傳到我的github上。
水之積也不厚,則其負大舟也無力
正如莊子在逍遙遊中說的,如果水不夠深,那就沒有能夠擔負大船的力量 。所以在真正開始涉及具體的程式碼之前,我們要先對MVC,MVP和MVVM做一個初步的瞭解。如果各位同學對此已經有所瞭解了,可以選擇性跳過這一節。
MVC
MVC,Model View Controller,是軟體架構中最常見的一種框架,簡單來說就是通過controller的控制去操作model層的資料,並且返回給view層展示,具體見下圖
當用戶出發事件的時候,view層會發送指令到controller層,接著controller去通知model層更新資料,model層更新完資料以後直接顯示在view層上,這就是MVC的工作原理。
那具體到Android上是怎麼樣一個情況呢?
大家都知道一個Android工程有什麼對吧,有java的class檔案,有res資料夾,裡面是各種資源,還有類似manifest檔案等等。對於原生的Android專案來說,layout.xml裡面的xml檔案就對應於MVC的view層,裡面都是一些view的佈局程式碼,而各種java bean,還有一些類似repository類就對應於model層,至於controller層嘛,當然就是各種activity咯。大家可以試著套用我上面說的MVC的工作原理是理解。比如你的介面有一個按鈕,按下這個按鈕去網路上下載一個檔案,這個按鈕是view層的,是使用xml來寫的,而那些和網路連線相關的程式碼寫在其他類裡,比如你可以寫一個專門的networkHelper類,這個就是model層,那怎麼連線這兩層呢?是通過button.setOnClickListener()這個函式,這個函式就寫在了activity中,對應於controller層。是不是很清晰。
大家想過這樣會有什麼問題嗎?顯然是有的,不然為什麼會有MVP和MVVM的誕生呢,是吧。問題就在於xml作為view層,控制能力實在太弱了,你想去動態的改變一個頁面的背景,或者動態的隱藏/顯示一個按鈕,這些都沒辦法在xml中做,只能把程式碼寫在activity中,造成了activity既是controller層,又是view層的這樣一個窘境。大家回想一下自己寫的程式碼,如果是一個邏輯很複雜的頁面,activity或者fragment是不是動輒上千行呢?這樣不僅寫起來麻煩,維護起來更是噩夢。(當然看過Android原始碼的同學其實會發現上千行的程式碼不算啥,一個RecyclerView.class的程式碼都快上萬行了呢。。)
MVC還有一個重要的缺陷,大家看上面那幅圖,view層和model層是相互可知的,這意味著兩層之間存在耦合,耦合對於一個大型程式來說是非常致命的,因為這表示開發,測試,維護都需要花大量的精力。
正因為MVC有這樣那樣的缺點,所以才演化出了MVP和MVVM這兩種框架。
MVP
MVP作為MVC的演化,解決了MVC不少的缺點,對於Android來說,MVP的model層相對於MVC是一樣的,而activity和fragment不再是controller層,而是純粹的view層,所有關於使用者事件的轉發全部交由presenter層處理。下面還是讓我們看圖
從圖中就可以看出,最明顯的差別就是view層和model層不再相互可知,完全的解耦,取而代之的presenter層充當了橋樑的作用,用於操作view層發出的事件傳遞到presenter層中,presenter層去操作model層,並且將資料返回給view層,整個過程中view層和model層完全沒有聯絡。看到這裡大家可能會問,雖然view層和model層解耦了,但是view層和presenter層不是耦合在一起了嗎?其實不是的,對於view層和presenter層的通訊,我們是可以通過介面實現的,具體的意思就是說我們的activity,fragment可以去實現實現定義好的介面,而在對應的presenter中通過介面呼叫方法。不僅如此,我們還可以編寫測試用的View,模擬使用者的各種操作,從而實現對Presenter的測試。這就解決了MVC模式中測試,維護難的問題。
當然,其實最好的方式是使用fragment作為view層,而activity則是用於建立view層(fragment)和presenter層(presenter)的一個控制器。
MVVM
MVVM最早是由微軟提出的
這裡要感謝泡在網上的日子,因為前面看到的三張圖我都是從它的部落格中摘取的,如果有人知道不允許這樣做的話請告訴我,我會從我的部落格中刪除的,謝謝。
從圖中看出,它和MVP的區別貌似不大,只不過是presenter層換成了viewmodel層,還有一點就是view層和viewmodel層是相互繫結的關係,這意味著當你更新viewmodel層的資料的時候,view層會相應的變動ui。
我們很難去說MVP和MVVM這兩個MVC的變種孰優孰劣,還是要具體情況具體分析。
紙上得來終覺淺,絕知此事要躬行
對於程式設計師來說,空談是最沒效率的一種方式,相信大家看了我上面對於三種模式的分析,或多或少都會有點雲裡霧裡,下面讓我們結合程式碼來看看。
讓我們試想一下下面這個情景,使用者點選一個按鈕A,獲取github上對應公司對應倉庫中貢獻排行第一的任的名字,然後我們還會有一個按鈕B,使用者點選按鈕B,介面上排行第一的那個人的名字就會換成自己的。
MVC
MVC實現是最簡單的。
首先看對應view層的xml檔案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/container" android:orientation="vertical" tools:context=".ui.view.MainActivity" android:fitsSystemWindows="true"> <Button android:text="get" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="get"/> <Button android:text="change" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="change"/> <TextView android:id="@+id/top_contributor" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textSize="30sp"/> </LinearLayout> |
很簡單,兩個Button一個TextView
接著看對應controller層的activity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
public class MainActivity extends AppCompatActivity { private ProcessDialog dialog; private Contributor contributor = new Contributor(); private TextView topContributor; private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() { public void onStart() { showProgress(); } public void onCompleted() { } public void onError(Throwable e) { } public void onNext(Contributor contributor) { MainActivity.this.contributor = contributor; topContributor.setText(contributor.login); dismissProgress(); } }; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); topContributor = (TextView)findViewById(R.id.top_contributor); } public void get(View view){ getTopContributor("square", "retrofit"); } public void change(View view){ contributor.login = "zjutkz"; topContributor.setText(contributor.login); } public void getTopContributor(String owner,String repo){ GitHubApi.getContributors(owner, repo) .take(1) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.newThread()) .map(new Func1<List<Contributor>, Contributor>() { public Contributor call(List<Contributor> contributors) { return contributors.get(0); } }) .subscribe(contributorSub); } public void showProgress(){ if(dialog == null){ dialog = new ProcessDialog(this); } dialog.showMessage("正在載入..."); } public void dismissProgress(){ if(dialog == null){ dialog = new ProcessDialog(this); } dialog.dismiss(); } } |
我們看一下get()方法中呼叫的getTopContributor方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public void getTopContributor(String owner,String repo){ GitHubApi.getContributors(owner, repo) .take(1) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.newThread()) .map(new Func1<List<Contributor>, Contributor>() { public Contributor call(List<Contributor> contributors) { return contributors.get(0); } }) .subscribe(contributorSub); } |
熟悉rxjava和retrofit的同學應該都明白這是啥意思,如果對這兩個開源庫不熟悉也沒事,可以參考給 Android 開發者的 RxJava 詳解和用 Retrofit 2 簡化 HTTP 請求這兩篇文章。
對於這裡大家只要知道這段程式碼的意思就是去獲取github上owner公司中的repo倉庫裡貢獻排名第一的那個人。貢獻者是通過Contributor這個java bean儲存的。
1 2 3 4 5 6 7 8 9 10 |
public class Contributor { public String login; public int contributions; public String toString() { return login + ", " + contributions; } } |
很簡單,login表示貢獻者的名字,contributor表示貢獻的次數。
然後通過rxjava的subscriber中的onNext()函式得到這個資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() { public void onStart() { showProgress(); } public void onCompleted() { } public void onError(Throwable e) { } public void onNext(Contributor contributor) { MainActivity.this.contributor = contributor; topContributor.setText(contributor.login); dismissProgress(); } }; |
至於另外那個change按鈕的工作大家應該都看得懂,這裡不重複了。
好了,我們來回顧一遍整個流程。
首先在xml中寫好佈局程式碼。
其次,activity作為一個controller,裡面的邏輯是監聽使用者點選按鈕並作出相應的操作。比如針對get按鈕,做的工作就是呼叫GithubApi的方法去獲取資料。
GithubApi,Contributor等類則表示MVC中的model層,裡面是資料和一些具體的邏輯操作。
說完了流程再來看看問題,還記得我們前面說的嗎,MVC在Android上的應用,一個具體的問題就是activity的責任過重,既是controller又是view。這裡是怎麼體現的呢?看了程式碼大家發現其中有一個progressDialog,在載入資料的時候顯示,載入完了以後取消,邏輯其實是view層的邏輯,但是這個我們沒辦法寫到xml裡面啊,包括TextView.setTextView(),這個也一樣。我們只能把這些邏輯寫到activity中,這就造成了activity的臃腫,這個例子可能還好,如果是一個複雜的頁面呢?大家自己想象一下。
MVP
通過具體的程式碼大家知道了MVC在Android上是如何工作的,也知道了它的缺點,那MVP是如何修正的呢?
這裡先向大家推薦github上的一個第三方庫,通過這個庫大家可以很輕鬆的實現MVP。好了,還是看程式碼吧。
首先還是xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/container" android:orientation="vertical" tools:context=".ui.view.MainActivity" android:fitsSystemWindows="true"> <Button android:text="get" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="get"/> <Button android:text="change" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="change"/> <TextView android:id="@+id/top_contributor" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textSize="30sp"/> </LinearLayout> |
這個和MVC是一樣的,畢竟介面的形式是一樣的嘛。
接下去,我們看一個介面。
1 2 3 4 5 6 7 8 |
|