MVC、MVP耗绿、MVVM的理解淺談

為什么要關注架構設計苹支?
因為假如你不關心架構,那么總有一天误阻,需要在同一個龐大的類中調試若干復雜的事情债蜜,你會發(fā)現(xiàn)在這樣的條件下晴埂,根本不可能在這個類中快速的找到以及有效的修改任何bug.當然,把這樣的一個類想象為一個整體是困難的寻定,因此儒洛,有可能一些重要的細節(jié)總會在這個過程中會被忽略。

在此狼速,我們可以定義一個好的架構應該具備的特點:
任務均衡分攤給具有清晰角色的實體

可測試性通常都來自與上一條(對于一個合適的架構是非常容易)

易用性和低成本維護

為什么采用分布式?
采用分布式可以在我們要弄清楚一些事情的原理時保持一個均衡的負載琅锻。如果你認為你的開發(fā)工作越多,你的大腦越能習慣復雜的思維向胡,其實這是對的恼蓬。但是,不能忽略的一個事實是僵芹,這種思維能力并不是線性增長的处硬,而且也并不能很快的到達峰值。所以拇派,能夠戰(zhàn)勝這種復雜性的最簡單的方法就是在遵循 單一功能原則 的前提下荷辕,將功能劃分給不同的實體。
為什么需要易測性攀痊?
其實這條要求對于哪些習慣了單元測試的人并不是一個問題桐腌,因為在添加了新的特性或者要增加一些類的復雜性之后通常會失效。這就意味著苟径,測試可以避免開發(fā)者在運行時才發(fā)現(xiàn)問題----當應用到達用戶的設備案站,每一次維護都需要浪費長達至少一周的時間才能再次分發(fā)給用戶。
為什么需要易用性棘街?
這個問題沒有固定的答案蟆盐,但值得一提的是,最好的代碼是那些從未寫過的代碼遭殉。因此石挂,代碼寫的越少,Bug就越少险污。這意味著希望寫更少的代碼不應該被單純的解釋為開發(fā)者的懶惰痹愚,而且也不應該因為偏愛更聰明的解決方案而忽視了它的維護開銷。
MV(X)系列概要
當今我們已經有很架構設計模式方面的選擇:

MVC

MVP

MVVM

三種設計模式都把一個應用中的實體分為以下三類:

視圖(View):用戶界面蛔糯。
控制器(Controller):業(yè)務邏輯
模型(Model):數據保存```
**Models**--負責主要的數據或者操作數據的數據訪問層拯腮,可以想象 Perspn 和 PersonDataProvider 類。

**Views**--負責展示層(GUI)蚁飒,可以聯(lián)想一下以 UI 開頭的所有類动壤。

**Controller/Presenter/ViewModel**--負責協(xié)調 Model 和 View,通常根據用戶在View上的動作在Model上作出對應的更改淮逻,同時將更改的信息返回到View上琼懊。

將實體進行劃分給我們帶來了以下好處:

1.更好的理解它們之間的關系
2.復用(尤其是對于View和Model)
3.獨立的測試

**一阁簸、MVC**

MVC,Model View Controller哼丈,是軟件架構中最常見的一種框架启妹,簡單來說就是通過controller的控制去操作model層的數據,并且返回給view層展示削祈,具體見下圖

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-309e4e5eb9cbec5f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

當用戶出發(fā)事件的時候翅溺,view層會發(fā)送指令到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層绍哎,控制能力實在太弱了来农,你想去動態(tài)的改變一個頁面的背景,或者動態(tài)的隱藏/顯示一個按鈕崇堰,這些都沒辦法在xml中做沃于,只能把代碼寫在activity中,造成了activity既是controller層海诲,又是view層的這樣一個窘境繁莹。大家回想一下自己寫的代碼,如果是一個邏輯很復雜的頁面特幔,activity或者fragment是不是動輒上千行呢咨演?這樣不僅寫起來麻煩,維護起來更是噩夢蚯斯。(當然看過Android源碼的同學其實會發(fā)現(xiàn)上千行的代碼不算啥薄风,一個RecyclerView.class的代碼都快上萬行了呢。拍嵌。)
MVC還有一個重要的缺陷遭赂,大家看上面那幅圖,view層和model層是相互可知的横辆,這意味著兩層之間存在耦合撇他,耦合對于一個大型程序來說是非常致命的,因為這表示開發(fā)龄糊,測試逆粹,維護都需要花大量的精力。
正因為MVC有這樣那樣的缺點炫惩,所以才演化出了MVP和MVVM這兩種框架僻弹。

**二嘲更、MVP**
MVP作為MVC的演化橱夭,解決了MVC不少的缺點斩披,對于Android來說徒爹,MVP的model層相對于MVC是一樣的艇潭,而activity和fragment不再是controller層诫硕,而是純粹的view層祠饺,所有關于用戶事件的轉發(fā)全部交由presenter層處理伍俘。下面還是讓我們看圖

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-53a5a00ab675f8ca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

從圖中就可以看出粘咖,最明顯的差別就是view層和model層不再相互可知蚣抗,完全的解耦,取而代之的presenter層充當了橋梁的作用瓮下,用于操作view層發(fā)出的事件傳遞到presenter層中翰铡,presenter層去操作model層钝域,并且將數據返回給view層,整個過程中view層和model層完全沒有聯(lián)系锭魔±ぃ看到這里大家可能會問,雖然view層和model層解耦了迷捧,但是view層和presenter層不是耦合在一起了嗎织咧?其實不是的,對于view層和presenter層的通信漠秋,我們是可以通過接口實現(xiàn)的笙蒙,具體的意思就是說我們的activity,fragment可以去實現(xiàn)實現(xiàn)定義好的接口膛堤,而在對應的presenter中通過接口調用方法手趣。不僅如此,我們還可以編寫測試用的View肥荔,模擬用戶的各種操作绿渣,從而實現(xiàn)對Presenter的測試。這就解決了MVC模式中測試燕耿,維護難的問題中符。

當然,其實最好的方式是使用fragment作為view層誉帅,而activity則是用于創(chuàng)建view層(fragment)和presenter層(presenter)的一個控制器淀散。
**三、MVVM**
MVVM最早是由微軟提出的

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5423625-fef0d11ff52bf658.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

從圖中看出蚜锨,它和MVP的區(qū)別貌似不大档插,只不過是presenter層換成了viewmodel層,還有一點就是view層和viewmodel層是相互綁定的關系亚再,這意味著當你更新viewmodel層的數據的時候郭膛,view層會相應的變動ui。

我們很難去說MVP和MVVM這兩個MVC的變種孰優(yōu)孰劣氛悬,還是要具體情況具體分析则剃。
[](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/#%E7%BA%B8%E4%B8%8A%E5%BE%97%E6%9D%A5%E7%BB%88%E8%A7%89%E6%B5%85%EF%BC%8C%E7%BB%9D%E7%9F%A5%E6%AD%A4%E4%BA%8B%E8%A6%81%E8%BA%AC%E8%A1%8C)紙上得來終覺淺,絕知此事要躬行
對于程序員來說如捅,空談是最沒效率的一種方式棍现,相信大家看了我上面對于三種模式的分析,或多或少都會有點云里霧里镜遣,下面讓我們結合代碼來看看己肮。
讓我們試想一下下面這個情景,用戶點擊一個按鈕A,獲取github上對應公司對應倉庫中貢獻排行第一的任的名字朴肺,然后我們還會有一個按鈕B窖剑,用戶點擊按鈕B,界面上排行第一的那個人的名字就會換成自己的戈稿。
[](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-1)MVC
MVC實現(xiàn)是最簡單的。
首先看對應view層的xml文件

<?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

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方法

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 開發(fā)者的 RxJava 詳解和用 Retrofit 2 簡化 HTTP 請求這兩篇文章跳昼。
對于這里大家只要知道這段代碼的意思就是去獲取github上owner公司中的repo倉庫里貢獻排名第一的那個人般甲。貢獻者是通過Contributor這個java bean存儲的。

public class Contributor {  
  
    public String login;  
    public int contributions;  
  
    @Override  
    public String toString() {  
        return login + ", " + contributions;  
    }  
}  ```
很簡單鹅颊,login表示貢獻者的名字敷存,contributor表示貢獻的次數。

然后通過rxjava的subscriber中的onNext()函數得到這個數據堪伍。

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彻亲,里面的邏輯是監(jiān)聽用戶點擊按鈕并作出相應的操作。比如針對get按鈕吮廉,做的工作就是調用GithubApi的方法去獲取數據苞尝。
GithubApi,Contributor等類則表示MVC中的model層宦芦,里面是數據和一些具體的邏輯操作宙址。
說完了流程再來看看問題,還記得我們前面說的嗎踪旷,MVC在Android上的應用曼氛,一個具體的問題就是activity的責任過重,既是controller又是view令野。這里是怎么體現(xiàn)的呢舀患?看了代碼大家發(fā)現(xiàn)其中有一個progressDialog,在加載數據的時候顯示气破,加載完了以后取消聊浅,邏輯其實是view層的邏輯,但是這個我們沒辦法寫到xml里面啊,包括TextView.setTextView()低匙,這個也一樣旷痕。我們只能把這些邏輯寫到activity中,這就造成了activity的臃腫顽冶,這個例子可能還好欺抗,如果是一個復雜的頁面呢?大家自己想象一下强重。
MVP
通過具體的代碼大家知道了MVC在Android上是如何工作的绞呈,也知道了它的缺點,那MVP是如何修正的呢间景?
這里先向大家推薦github上的一個第三方庫佃声,通過這個庫大家可以很輕松的實現(xiàn)MVP。好了倘要,還是看代碼吧圾亏。
首先還是xml

<?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是一樣的,畢竟界面的形式是一樣的嘛封拧。

接下去志鹃,我們看一個接口。

public interface ContributorView extends MvpView {

void onLoadContributorStart();  

void onLoadContributorComplete(Contributor topContributor);  

void onChangeContributorName(String name);  

} ```
這個接口起什么作用呢哮缺?還記得我之前說的嗎弄跌?MVP模式中,view層和presenter層靠的就是接口進行連接尝苇,而具體的就是上面的這個了铛只,里面定義的三個方法,第一個是開始獲取數據糠溜,第二個是獲取數據成功淳玩,第三個是改名。我們的view層(activity)只要實現(xiàn)這個接口就可以了非竿。

下面看activity的代碼

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蜕着,實現(xiàn)了剛才的ContributorView接口。繼承的那個MvpActivity大家這里不用太關心主要是做了一些初始化和生命周期的封裝红柱。我們只要關心這個activity作為view層承匣,到底是怎么工作的。

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

public void change(View view){
getPresenter().change();
} ```
get()和change()這兩個方法是我們點擊按鈕以后執(zhí)行的锤悄,可以看到韧骗,里面完完全全沒有任何和model層邏輯相關的東西,只是簡單的委托給了presenter零聚,那我們再看看presenter層做了什么

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是實現(xiàn)了這個接口的,自然會走到對應的方法中政模。
好了岗宣,我們來捋一捋。
首先淋样,和MVC最大的不同耗式,MVP把activity作為了view層,通過代碼也可以看到习蓬,整個activity沒有任何和model層相關的邏輯代碼纽什,取而代之的是把代碼放到了presenter層中,presenter獲取了model層的數據之后躲叼,通過接口的形式將view層需要的數據返回給它就OK了。
這樣的好處是什么呢企巢?首先枫慷,activity的代碼邏輯減少了,其次浪规,view層和model層完全解耦或听,具體來說,如果你需要測試一個http請求是否順利笋婿,你不需要寫一個activity誉裆,只需要寫一個java類,實現(xiàn)對應的接口缸濒,presenter獲取了數據自然會調用相應的方法足丢,相應的,你也可以自己在presenter中mock數據庇配,分發(fā)給view層斩跌,用來測試布局是否正確。
[](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/#MVVM-1)MVVM
首先在看這段內容之前捞慌,你需要保證你對data binding框架有基礎的了解耀鸦。不了解的同學可以去看下這篇文章。在接下去讓我們開始探索MVVM啸澡,MVVM最近在Android上可謂十分之火袖订,最主要的原因就是谷歌推出了data binding這個框架,可以輕松的實現(xiàn)MVVM嗅虏。但是洛姑,我在網上查閱關于Android的data binding資料的時候,發(fā)現(xiàn)國內有很多人都誤解了旋恼,首先吏口,我們從一篇錯誤的文章開始奄容。當然我在這里引用這篇文章也是對事不對人,如果對文章的作者產生了不好的影響我這里說一聲抱歉产徊。
上面那篇文章是一個關于data binding的使用昂勒,看起來很美好,但是舟铜,其中有一個錯誤可以說是非常戈盈,非常,非常嚴重的谆刨。
![1460565976130825.png](http://upload-images.jianshu.io/upload_images/5423625-5397a479448a3103.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
它竟然說data binding的viewmodel層是binding類塘娶,其實不止是這篇文章,其他有一些開發(fā)者寫的關于data binding的文章里都犯了一樣的錯誤痊夭。大家如果也有這樣的概念刁岸,請務必糾正過來!她我!
說完了錯誤的概念虹曙,那data binding中真正的viewmodel是什么呢?我們還是以之前MVC番舆,MVP的那個例子做引導酝碳。
首先是view層,這沒啥好說的恨狈,和MVP一樣疏哗,view層就是xml和activity。

<?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> ```

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類!大家不要驚訝坦袍,我慢慢的來說十厢。

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類的工作是什么普碎?

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中間的那根線叭悸摺!臂痕!
[](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/#%E7%9C%9F%E7%90%86%E5%9C%A8%E8%8D%92%E8%B0%AC%E8%A2%AB%E8%AF%81%E5%AE%9E%E4%BB%A5%E5%89%8D%EF%BC%8C%E9%83%BD%E5%8F%AA%E6%98%AF%E6%9A%97%E5%AE%A4%E9%87%8C%E7%9A%84%E8%A3%85%E9%A5%B0)真理在荒謬被證實以前伯襟,都只是暗室里的裝飾
前面討論了MVC,MVP和MVVM具體的實現(xiàn)方案握童,大家肯定都了解了它們三者的關系和使用方式姆怪。但是,這里我想說澡绩,不要把一個框架看作萬能的稽揭,其實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/#MVP-2)MVP
MVP的問題在于溪掀,由于我們使用了接口的方式去連接view層和presenter層,這樣就導致了一個問題步鉴,如果你有一個邏輯很復雜的頁面揪胃,你的接口會有很多璃哟,十幾二十個都不足為奇。想象一個app中有很多個這樣復雜的頁面喊递,維護接口的成本就會非常的大随闪。
這個問題的解決方案就是你得根據自己的業(yè)務邏輯去斟酌著寫接口。你可以定義一些基類接口册舞,把一些公共的邏輯蕴掏,比如網絡請求成功失敗,toast等等放在里面调鲸,之后你再定義新的接口的時候可以繼承自那些基類盛杰,這樣會好不少。
[](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/#MVVM-2)MVVM
MVVM的問題呢藐石,其實和MVC有一點像即供。data binding框架解決了數據綁定的問題,但是view層還是會過重于微,大家可以看我上面那個MVVM模式下的activity

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);  
}  

} ```
大家有沒有發(fā)現(xiàn)逗嫡,activity在MVVM中應該是view層的,但是里面卻和MVC一樣寫了對model的處理株依。有人會說你可以把對model的處理放到viewmodel層中驱证,這樣不是更符合MVVM的設計理念嗎?這樣確實可以恋腕,但是progressDialog的show和dismiss呢抹锄?你怎么在viewmodel層中控制?這是view層的東西啊荠藤,而且在xml中也沒有伙单,我相信會有解決的方案,但是我們有沒有一種更加便捷的方式呢哈肖?
路漫漫其修遠兮吻育,吾將上下而求索
其實,真正的最佳實踐都是人想出來的淤井,我們?yōu)楹尾唤Y合一下MVP和MVVM的特點呢布疼?其實谷歌已經做了這樣的事,大家可以看下這個庄吼。沒錯缎除,就是MVP+data binding,我們可以使用presenter去做和model層的通信总寻,并且使用data binding去輕松的bind data器罐。還是讓我們看代碼吧。
首先還是view層渐行。

<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>  ```

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層

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框架去節(jié)省了類似findViewById和數據綁定的時間铸董,又使用了presenter去將業(yè)務邏輯和view層分離。

當然這也不是固定的肴沫,你大可以在viewmodel中實現(xiàn)相應的接口粟害,presenter層的數據直接發(fā)送到viewmodel中,在viewmodel里更新颤芬,因為view和viewmodel是綁定的悲幅,這樣view也會相應的作出反應。

說到這里站蝠,我還是想重復剛才的那句話汰具,最佳實踐都是人想出來的,用這些框架根本的原因也是為了盡量低的耦合性和盡量高的可復用性菱魔。
實例代碼下載:https://github.com/gb0302/MvPDemo
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末留荔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子澜倦,更是在濱河造成了極大的恐慌聚蝶,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藻治,死亡現(xiàn)場離奇詭異碘勉,居然都是意外死亡,警方通過查閱死者的電腦和手機桩卵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門恰聘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吸占,你說我怎么就攤上這事≡浔觯” “怎么了矾屯?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長初厚。 經常有香客問我件蚕,道長,這世上最難降的妖魔是什么产禾? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任排作,我火速辦了婚禮,結果婚禮上亚情,老公的妹妹穿的比我還像新娘妄痪。我一直安慰自己,他們只是感情好楞件,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布衫生。 她就那樣靜靜地躺著裳瘪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪罪针。 梳的紋絲不亂的頭發(fā)上彭羹,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音泪酱,去河邊找鬼派殷。 笑死,一個胖子當著我的面吹牛墓阀,可吹牛的內容都是我干的毡惜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼岂津,長吁一口氣:“原來是場噩夢啊……” “哼虱黄!你這毒婦竟也來了?” 一聲冷哼從身側響起吮成,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤橱乱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后粱甫,有當地人在樹林里發(fā)現(xiàn)了一具尸體泳叠,經...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年茶宵,在試婚紗的時候發(fā)現(xiàn)自己被綠了危纫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡乌庶,死狀恐怖种蝶,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情瞒大,我是刑警寧澤螃征,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站透敌,受9級特大地震影響盯滚,放射性物質發(fā)生泄漏。R本人自食惡果不足惜酗电,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一魄藕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撵术,春花似錦背率、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽移稳。三九已至,卻和暖如春会油,著一層夾襖步出監(jiān)牢的瞬間个粱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工翻翩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留都许,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓嫂冻,卻偏偏與公主長得像胶征,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子桨仿,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內容