選擇恐懼症的福音!教你認清MVC,MVP和MVVM(轉:示例挺好,不太贊同畫圖)

轉自:http://zjutkz.net/2016/04/13/%E9%80%89%E6%8B%A9%E6%81%90%E6%83%A7%E7%97%87%E7%9A%84%E7%A6%8F%E9%9F%B3%EF%BC%81%E6%95%99%E4%BD%A0%E8%AE%A4%E6%B8%85MVC%EF%BC%8CMVP%E5%92%8CMVVM/

 

選擇恐懼症的福音!教你認清MVC,MVP和MVVM

相信你們對MVC,MVP和MVVM都不陌生,做爲三個最耳熟能詳的Android框架,它們的應用能夠是很是普遍的,可是對於一些新手來講,可能對於區分它們三個都有困難,更別說在實際的項目中應用了,有些時候想用MVP的,代碼寫着寫着就變成了MVC,長此以往就對它們三個的選擇產生了恐懼感,若是你也是這樣的人羣,那麼這篇文章可能會對你有很大的幫助,但願你們看完都會有收穫吧!php

文章重點:html

(1)瞭解並區分MVC,MVP,MVVM。java

(2)知道這三種模式在Android中如何使用。android

(3)走出data binding的誤區。git

(4)瞭解MVP+data binding的開發模式。程序員

本篇文章的demo我將會上傳到個人github上github

本文已獨家受權微信公衆號 AndroidDeveloper ,拒絕其餘任何形式的轉載微信

本文已獨家受權微信公衆號 AndroidDeveloper ,拒絕其餘任何形式的轉載網絡

本文已獨家受權微信公衆號 AndroidDeveloper ,拒絕其餘任何形式的轉載架構

重要的事情說三遍

水之積也不厚,則其負大舟也無力

正如莊子在逍遙遊中說的,若是水不夠深,那就沒有可以擔負大船的力量 。因此在真正開始涉及具體的代碼以前,咱們要先對MVC,MVP和MVVM作一個初步的瞭解。若是各位同窗對此已經有所瞭解了,能夠選擇性跳過這一節。

MVC

MVC,Model View Controller,是軟件架構中最多見的一種框架,簡單來講就是經過controller的控制去操做model層的數據,而且返回給view層展現,具體見下圖

mvc

當用戶出發事件的時候,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層處理。下面仍是讓咱們看圖

mvp

從圖中就能夠看出,最明顯的差異就是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最先是由微軟提出的

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>() {

@Override
public void onStart() {
showProgress();
}

@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onNext(Contributor contributor) {
MainActivity.this.contributor = contributor;

topContributor.setText(contributor.login);

dismissProgress();
}
};

@Override
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>() {

@Override
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>() {

@Override
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;

@Override
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>() {

@Override
public void onStart() {
showProgress();
}

@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {

}

@Override
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
public interface ContributorView extends MvpView {

void onLoadContributorStart();

void onLoadContributorComplete(Contributor topContributor);

void onChangeContributorName(String name);
}

這個接口起什麼做用呢?還記得我以前說的嗎?MVP模式中,view層和presenter層靠的就是接口進行鏈接,而具體的就是上面的這個了,裏面定義的三個方法,第一個是開始獲取數據,第二個是獲取數據成功,第三個是更名。咱們的view層(activity)只要實現這個接口就能夠了。

下面看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
public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {

private ProcessDialog dialog;

private TextView topContributor;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
topContributor = (TextView)findViewById(R.id.top_contributor);
}

@NonNull
@Override
public ContributorPresenter createPresenter() {
return new ContributorPresenter();
}

public void get(View view){
getPresenter().get("square", "retrofit");
}

public void change(View view){
getPresenter().change();
}

@Override
public void onLoadContributorStart() {
showProgress();
}

@Override
public void onLoadContributorComplete(Contributor contributor) {

topContributor.setText(contributor.toString());

dismissProgress();
}

@Override
public void onChangeContributorName(String name) {
topContributor.setText(name);
}

public void showProgress(){
if(dialog == null){
dialog = new ProcessDialog(this);
}

dialog.showMessage("正在加載...");
}

public void dismissProgress(){
if(dialog == null){
dialog = new ProcessDialog(this);
}

dialog.dismiss();
}
}

它繼承自MvpActivity,實現了剛纔的ContributorView接口。繼承的那個MvpActivity你們這裏不用太關心主要是作了一些初始化和生命週期的封裝。咱們只要關心這個activity做爲view層,究竟是怎麼工做的。

1
2
3
4
5
6
7
public void get(View view){
getPresenter().get("square", "retrofit");
}

public void change(View view){
getPresenter().change();
}

get()和change()這兩個方法是咱們點擊按鈕之後執行的,能夠看到,裏面完徹底全沒有任何和model層邏輯相關的東西,只是簡單的委託給了presenter,那咱們再看看presenter層作了什麼

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
public class ContributorPresenter extends MvpBasePresenter<ContributorView> {

private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

@Override
public void onStart() {
ContributorView view = getView();
if(view != null){
view.onLoadContributorStart();
}
}

@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onNext(Contributor topContributor) {
ContributorView view = getView();
if(view != null){
view.onLoadContributorComplete(topContributor);
}
}
};

public void get(String owner,String repo){
GitHubApi.getContributors(owner, repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(new Func1<List<Contributor>, Contributor>() {

@Override
public Contributor call(List<Contributor> contributors) {
return contributors.get(0);
}
})
.subscribe(contributorSub);
}

public void change(){
ContributorView view = getView();
if(view != null){
view.onChangeContributorName("zjutkz");
}
}
}

其實就是把剛纔MVC中activity的那部分和model層相關的邏輯抽取了出來,而且在相應的時機調用ContributorView接口對應的方法,而咱們的activity是實現了這個接口的,天然會走到對應的方法中。

好了,咱們來捋一捋。

首先,和MVC最大的不一樣,MVP把activity做爲了view層,經過代碼也能夠看到,整個activity沒有任何和model層相關的邏輯代碼,取而代之的是把代碼放到了presenter層中,presenter獲取了model層的數據以後,經過接口的形式將view層須要的數據返回給它就OK了。

這樣的好處是什麼呢?首先,activity的代碼邏輯減小了,其次,view層和model層徹底解耦,具體來講,若是你須要測試一個http請求是否順利,你不須要寫一個activity,只須要寫一個java類,實現對應的接口,presenter獲取了數據天然會調用相應的方法,相應的,你也能夠本身在presenter中mock數據,分發給view層,用來測試佈局是否正確。

MVVM

首先在看這段內容以前,你須要保證你對data binding框架有基礎的瞭解。不瞭解的同窗能夠去看下這篇文章。在接下去讓咱們開始探索MVVM,MVVM最近在Android上可謂十分之火,最主要的緣由就是谷歌推出了data binding這個框架,能夠輕鬆的實現MVVM。可是,我在網上查閱關於Android的data binding資料的時候,發現國內有不少人都誤解了,首先,咱們從一篇錯誤的文章開始。固然我在這裏引用這篇文章也是對事不對人,若是對文章的做者產生了很差的影響我這裏說一聲抱歉。

上面那篇文章是一個關於data binding的使用,看起來很美好,可是,其中有一個錯誤能夠說是很是,很是,很是嚴重的。

wrong_viewmodel

它居然說data binding的viewmodel層是binding類,其實不止是這篇文章,其餘有一些開發者寫的關於data binding的文章裏都犯了同樣的錯誤。你們若是也有這樣的概念,請務必糾正過來!!

說完了錯誤的概念,那data binding中真正的viewmodel是什麼呢?咱們仍是以以前MVC,MVP的那個例子作引導。

首先是view層,這沒啥好說的,和MVP同樣,只不過多了數據綁定。view層就是xml和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
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="contributor" type="zjutkz.com.mvvm.viewmodel.Contributor"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
android:fitsSystemWindows="true">

<Button
android:id="@+id/get"
android:text="get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get"/>

<Button
android:id="@+id/change"
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"
android:text="@{contributor.login}"/>
</LinearLayout>

</layout>
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
public class MainActivity extends AppCompatActivity {

private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

@Override
public void onStart() {
showProgress();
}

@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onNext(Contributor contributor) {
binding.setContributor(contributor);

dismissProgress();
}
};

private ProcessDialog dialog;

private MvvmActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);
}

public void get(View view){
getContributors("square", "retrofit");
}

public void change(View view){
if(binding.getContributor() != null){
binding.getContributor().setLogin("zjutkz");
}
}

public void showProgress(){
if(dialog == null){
dialog = new ProcessDialog(this);
}

dialog.showMessage("正在加載...");
}

public void dismissProgress(){
if(dialog == null){
dialog = new ProcessDialog(this);
}

dialog.dismiss();
}

public void getContributors(String owner,String repo){
GitHubApi.getContributors(owner, repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(new Func1<List<Contributor>, Contributor>() {

@Override
public Contributor call(List<Contributor> contributors) {
return contributors.get(0);
}
})
.subscribe(contributorSub);
}
}

若是你對data binding框架是有了解的,上面的代碼你能輕鬆的看懂。

那model層又是什麼呢?固然就是那些和數據相關的類,GithubApi等等。

重點來了,viewmodel層呢?好吧,viewmodel層就是是Contributor類!你們不要驚訝,我慢慢的來講。

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
public class Contributor extends BaseObservable{

private String login;
private int contributions;

@Bindable
public String getLogin(){
return login;
}

@Bindable
public int getContributions(){
return contributions;
}

public void setLogin(String login){
this.login = login;
notifyPropertyChanged(BR.login);
}

public void setContributions(int contributions){
this.contributions = contributions;
notifyPropertyChanged(BR.contributions);
}

@Override
public String toString() {
return login + ", " + contributions;
}
}

咱們能夠看到,Contributor和MVP相比,繼承自了BaseObservable,有基礎的同窗都知道這是爲了當Contributor內部的variable改變的時候ui能夠同步的做出響應。

我爲何說Contributor是一個viewmodel呢。你們還記得viewmodel的概念嗎?view和viewmodel相互綁定在一塊兒,viewmodel的改變會同步到view層,從而view層做出響應。這不就是Contributor和xml中那些組件元素的關係嗎?因此,你們不要被binding類迷惑了,data binding框架中的viewmodel是本身定義的那些看似是model類的東西!好比這裏的Contributor!

話說到這裏,那binding類又是什麼呢?其實具體對應到以前MVVM的那張圖就很好理解了,咱們想一下,binding類的工做是什麼?

1
2
3
binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);

binding.setContributor(contributor);

首先,binding要經過DataBindingUtil.setContentView()方法將xml,也就是view層設定。

接着,經過setXXX()方法將viewmodel層注入進去。

因爲這兩個工做,view層(xml的各個組件)和viewmodel層(contributor)綁定在了一塊兒。

好了,你們知道了嗎,binding類,其實就是上圖中view和viewmodel中間的那根線啊!!

真理在荒謬被證明之前,都只是暗室裏的裝飾

前面討論了MVC,MVP和MVVM具體的實現方案,你們確定都瞭解了它們三者的關係和使用方式。可是,這裏我想說,不要把一個框架看做萬能的,其實MVP和MVVM都是有本身的缺陷的!下面我一一來講。

MVP

MVP的問題在於,因爲咱們使用了接口的方式去鏈接view層和presenter層,這樣就致使了一個問題,若是你有一個邏輯很複雜的頁面,你的接口會有不少,十幾二十個都不足爲奇。想象一個app中有不少個這樣複雜的頁面,維護接口的成本就會很是的大。

這個問題的解決方案就是你得根據本身的業務邏輯去斟酌着寫接口。你能夠定義一些基類接口,把一些公共的邏輯,好比網絡請求成功失敗,toast等等放在裏面,以後你再定義新的接口的時候能夠繼承自那些基類,這樣會好很多。

MVVM

MVVM的問題呢,其實和MVC有一點像。data binding框架解決了數據綁定的問題,可是view層仍是會太重,你們能夠看我上面那個MVVM模式下的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
public class MainActivity extends AppCompatActivity {

private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

@Override
public void onStart() {
showProgress();
}

@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onNext(Contributor contributor) {
binding.setContributor(contributor);

dismissProgress();
}
};

private ProcessDialog dialog;

private MvvmActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);
}

public void get(View view){
getContributors("square", "retrofit");
}

public void change(View view){
if(binding.getContributor() != null){
binding.getContributor().setLogin("zjutkz");
}
}

public void showProgress(){
if(dialog == null){
dialog = new ProcessDialog(this);
}

dialog.showMessage("正在加載...");
}

public void dismissProgress(){
if(dialog == null){
dialog = new ProcessDialog(this);
}

dialog.dismiss();
}

public void getContributors(String owner,String repo){
GitHubApi.getContributors(owner, repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(new Func1<List<Contributor>, Contributor>() {

@Override
public Contributor call(List<Contributor> contributors) {
return contributors.get(0);
}
})
.subscribe(contributorSub);
}
}

你們有沒有發現,activity在MVVM中應該是view層的,可是裏面卻和MVC同樣寫了對model的處理。有人會說你能夠把對model的處理放到viewmodel層中,這樣不是更符合MVVM的設計理念嗎?這樣確實能夠,可是progressDialog的show和dismiss呢?你怎麼在viewmodel層中控制?這是view層的東西啊,並且在xml中也沒有,我相信會有解決的方案,可是咱們有沒有一種更加便捷的方式呢?

路漫漫其修遠兮,吾將上下而求索

其實,真正的最佳實踐都是人想出來的,咱們爲什麼不結合一下MVP和MVVM的特色呢?其實谷歌已經作了這樣的事,你們能夠看下這個。沒錯,就是MVP+data binding,咱們可使用presenter去作和model層的通訊,而且使用data binding去輕鬆的bind data。仍是讓咱們看代碼吧。

首先仍是view層。

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
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="contributor" type="zjutkz.com.mvpdatabinding.viewmodel.Contributor"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
android:fitsSystemWindows="true">

<Button
android:id="@+id/get"
android:text="get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get"/>

<Button
android:id="@+id/change"
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"
android:text="@{contributor.login}"/>
</LinearLayout>

</layout>
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
public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {

private ProcessDialog dialog;

private ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
}

@NonNull
@Override
public ContributorPresenter createPresenter() {
return new ContributorPresenter();
}

public void get(View view){
getPresenter().get("square", "retrofit");
}

public void change(View view){
if(binding.getContributor() != null){
binding.getContributor().setLogin("zjutkz");
}
}

@Override
public void onLoadContributorStart() {
showProgress();
}

@Override
public void onLoadContributorComplete(Contributor contributor) {
binding.setContributor(contributor);
dismissProgress();
}

public void showProgress(){
if(dialog == null){
dialog = new ProcessDialog(this);
}

dialog.showMessage("正在加載...");
}

public void dismissProgress(){
if(dialog == null){
dialog = new ProcessDialog(this);
}

dialog.dismiss();
}
}

而後是presenter層

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
public class ContributorPresenter extends MvpBasePresenter<ContributorView> {

private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

@Override
public void onStart() {
ContributorView view = getView();
if(view != null){
view.onLoadContributorStart();
}
}

@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onNext(Contributor topContributor) {
ContributorView view = getView();
if(view != null){
view.onLoadContributorComplete(topContributor);
}
}
};

public void get(String owner,String repo){
GitHubApi.getContributors(owner, repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(new Func1<List<Contributor>, Contributor>() {

@Override
public Contributor call(List<Contributor> contributors) {
return contributors.get(0);
}
})
.subscribe(contributorSub);
}
}

model層就是GithubApi等等。

咱們使用了data binding框架去節省了相似findViewById和數據綁定的時間,又使用了presenter去將業務邏輯和view層分離。

固然這也不是固定的,你大能夠在viewmodel中實現相應的接口,presenter層的數據直接發送到viewmodel中,在viewmodel裏更新,由於view和viewmodel是綁定的,這樣view也會相應的做出反應。

說到這裏,我仍是想重複剛纔的那句話,最佳實踐都是人想出來的,用這些框架根本的緣由也是爲了儘可能低的耦合性和儘可能高的可複用性。

相關文章
相關標籤/搜索