MVP 與 MVC 簡介
MVP 軟件架構(gòu)在現(xiàn)在的應(yīng)用中, 特別是 Android 端的編程中尤為突出的使用,因為 MVP 架構(gòu)可以很深層次的去解耦視圖、業(yè)務(wù)邏輯、數(shù)據(jù)源三者的關(guān)系,讓它們之間的相互依賴性降低。MVP 是針對 Android 端開發(fā)而言的,它其實是 MVC 演變過來的,因為 MVC 模式在 Android 開發(fā)中并不是那么愉快。主要表現(xiàn)方面,比如,我們在寫一個功能模塊時,Activity 中的代碼很容易就突破到上千甚至上萬行,除了必要的 findviewid,listener 等,其他的代碼幾乎都是業(yè)務(wù)邏輯相關(guān)的,這就顯得 Activity 非常的臃腫,也不是說不可以,但是在模塊升級時造成業(yè)務(wù)邏輯的改變,我們就需要去成千上萬行的 Activity 中尋找業(yè)務(wù)邏輯代碼,這就有可能出現(xiàn)多處代碼需要修改,不細心的話,非常容易出 Bug,而且除了 Bug 代碼也不好定位。
第二個問題,數(shù)據(jù)源(來自數(shù)據(jù)庫或這網(wǎng)絡(luò))部分可能會寫好幾份,比如,有兩個 Activity 同時有著個人信息數(shù)據(jù)要顯示,我必須在這兩個 Activity 中各請求一次網(wǎng)絡(luò)或者訪問數(shù)據(jù)庫,得到最新的數(shù)據(jù),這就出現(xiàn)了代碼重復(fù)的問題。
帶著這兩個問題,我們來看看 MVP 架構(gòu)能夠怎樣幫助我們解決 MVC 出現(xiàn)的這種問題!首先,我來簡單的介紹一下 MVC 它指的是:
- model(模型),負責訪問網(wǎng)絡(luò)數(shù)據(jù)、訪問數(shù)據(jù)庫數(shù)據(jù),提供數(shù)據(jù)源
- view(視圖),負責更新界面、響應(yīng)用戶界面操作
- controller(控制器),負責業(yè)務(wù)邏輯控制,處理數(shù)據(jù)
我們在 Android 開發(fā)中,Activity 負責 UI 顯示及更新操作,相當于 View 層,我們一般都會把代碼直接寫在 Activity 中,不管是數(shù)據(jù)源、邏輯控制、數(shù)據(jù)處理等操作一個勁的往 Activity 里寫,那么它們?nèi)叩年P(guān)系都互相依賴,你中有我,我中有你,它們就形成了三角的關(guān)系。

這樣的關(guān)系,導(dǎo)致 Activity 代碼量過于龐大,修改和維護起來比較困難。這樣的三角戀關(guān)系,顯然不是長久之際。于是,MVP 它就強行把 View 與 Model 隔離了,讓它們不再有聯(lián)系,傳說中的 Presenter 就是腳踏兩只船,來回奔走于 View 與 Model 之間的契約者、聯(lián)系者。

MVP(model、view 、presenter),將 Activity 中的代碼抽離了出來,把 Activity(Fragment、View) 只當作 View 層,通過一個 Presenter(契約層)將 View 和 Model 層聯(lián)系起來,讓 View 和 Model 層充分的解耦。
我們訪問網(wǎng)絡(luò)得到數(shù)據(jù)并顯示出來會是這樣一個流程:
- Activity 啟動時,告訴 Presenter 我要數(shù)據(jù)了
- Presenter 就會叫 Model 去訪問數(shù)據(jù)接口得到數(shù)據(jù)
- Model 得到原數(shù)據(jù)就交給 Presenter 了,Presenter 一看數(shù)據(jù)不規(guī)范,趕緊處理一下,處理完成
- Presenter 就把處理后的數(shù)據(jù)匯報給 Activity(View),View 拿到數(shù)據(jù)就做顯示的操作,工作結(jié)束
MVC
下面我們通過一個簡單的案例來進行了解 MVP 架構(gòu)的代碼寫法,當然,這里的寫法不唯一,因為每個人寫代碼的方式都是不一樣的,最核心的是 MVP 架構(gòu)的思想,把思想轉(zhuǎn)為實際代碼,這才是值得我們探索和學(xué)習(xí)的地方。
來看看代碼,我們在網(wǎng)絡(luò)請求時,獲取到數(shù)據(jù),并 toast 和顯示到 textview 上,這樣的代碼我相信誰都會寫,但一般人都是直接一口氣寫在 MainActivity 類上面,比如這樣:
public class MainActivity extends AppCompatActivity { private TextView tv; private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); request(); showDialog(); } private void initViews() { tv = findViewById(R.id.tv); } private void showDialog() { ProgressDialog dialog = new ProgressDialog(this); dialog.show(); new Handler().postDelayed(new Runnable() { @Override public void run() { dialog.dismiss(); } }, 1500); } private void request() { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("https://www.baidu.com/") .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { String resp = response.body().string(); toast(resp); } }); } private void toast(String resp) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "" + resp, Toast.LENGTH_SHORT).show(); tv.setText(resp); } }); } }
它的效果其實就是請求百度首頁,返回網(wǎng)頁數(shù)據(jù)以 string 方式顯示出來而已。

上面的代碼,其實就是 MVC 架構(gòu)的寫法,把 Model(網(wǎng)頁數(shù)據(jù))、Presenter(數(shù)據(jù)處理)、View(activity)三層寫在了一起,雖然沒什么問題,但我們看看 MainActivity 中的代碼比較雜亂,在業(yè)務(wù)代碼比較多的情況下,MainActivity 更加的沉重,不利于維護與業(yè)務(wù)升級。
MVP 實戰(zhàn)
那么我們下面就要將這個類中的代碼改寫為 MVP 的寫法,回顧上面提及的 MVP 架構(gòu)的思想,它是將 View 層與 Model 層徹底隔離,意味著 View 和 Model 都不再持對方的引用,它們通過一個第三者 Presenter 來代理事物的傳遞,所以 Presenter 層會持有 Model 與 View 層的引用,這是第一步。
第二步,是將它們之間的聯(lián)系抽象出來,以接口的方式相互調(diào)用,所以 Model 、View、Presenter 各自擁有自己的接口和抽象方法,所以這就會無形的多出了三個接口類,這也就是 MVP 的缺點之一。所以,為了較少的創(chuàng)建接口類,我們就給這三層接口定義了一個契約接口,把它們更加緊密的結(jié)合在一起,方法查看,例如代碼這樣寫:
(1)契約類:
package com.test.mvp.mvpdemo.mvp.v1; import okhttp3.Callback; /** * 契約接口,可以很直觀的看到 M、V、P 層接口中提供的方法 */ public interface MainContract { interface IMainModel { void requestBaidu(Callback callback); } interface IMainView { void showDialog(); void succes(String content); } interface IMainPresenter { void handlerData(); } }
然后,再將之前的一個單獨的 MainActivity 分包,分別創(chuàng)建 Model 層實現(xiàn)類、Presenter 層實現(xiàn)類、MainActivity 就相當于View 層,這樣一來架構(gòu)就更加清晰明了:

接著,分別給這三層實現(xiàn)我們剛剛寫的 MainContract 中相對應(yīng)的接口,我們先來看看 Model 層,它就主要負責網(wǎng)絡(luò)請求,也就是我們的 OKHttp 請求到百度首頁的一個操作,很簡單的代碼。
(2)Model 層:
package com.test.mvp.mvpdemo.mvp.v1.model; import com.test.mvp.mvpdemo.mvp.v1.MainContract; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; /** * model 層,請求網(wǎng)絡(luò)或數(shù)據(jù)庫,提供數(shù)據(jù)源(原始數(shù)據(jù)) */ public class DataModel implements MainContract.IMainModel { @Override public void requestBaidu(Callback callback) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("https://www.baidu.com/") .build(); client.newCall(request).enqueue(callback); } }
這里需要傳入一個 OKHttp 的 Callback ,由于不能在 Model 層中處理業(yè)務(wù)數(shù)據(jù),所以我們讓它自己回調(diào)給 Presenter 層來處理,保證數(shù)據(jù)的原始狀態(tài)。
(3)Presenter 層:
然后是 Presenter 層,它是處理業(yè)務(wù)邏輯和業(yè)務(wù)數(shù)據(jù)的,所以必須持有 Model 的引用,同時要將處理完的數(shù)據(jù)交給 View 層用于顯示,也必須持有 View 的引用,那么,一開始我們就要把這兩層給實例化,具體看下面的代碼:
package com.test.mvp.mvpdemo.mvp.v1.presenter; import com.test.mvp.mvpdemo.mvp.v1.MainContract; import com.test.mvp.mvpdemo.mvp.v1.model.DataModel; import JAVA.io.IOException; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; /** * presenter 層,承擔業(yè)務(wù)邏輯處理,數(shù)據(jù)源處理等 */ public class MainPresenter implements MainContract.IMainPresenter { private MainContract.IMainModel mModel; private MainContract.IMainView mView; public MainPresenter(MainContract.IMainView view) { this.mView = view; mModel = new DataModel(); } @Override public void handlerData() { if (mView != null) { mView.showDialog(); } /** * 發(fā)起請求,獲得回調(diào)數(shù)據(jù) */ mModel.requestBaidu(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { String content = response.body().string(); if (mView != null) { mView.succes(content); } } }); } }
這里的處理數(shù)據(jù)我就不做說明了,注意一點,這里是請求網(wǎng)絡(luò)數(shù)據(jù)會有延時,我們 Presenter 層持有了 View 層的引用,也就是 Activity 的引用,在網(wǎng)絡(luò)堵塞的情況下,用戶在打開 Activity 又馬上給關(guān)閉了,這時 View 相當于被摧毀了,如果不進行判斷 View 引用是否為空,當數(shù)據(jù)在延遲幾秒后返回時,就會造成空指針異常,所以每次都要進行判斷才合理。
最后,在 Presenter 層處理完了數(shù)據(jù),就要將數(shù)據(jù)傳達給 View 層顯示,所以要調(diào)用 View 接口的抽象方法,把數(shù)據(jù)參數(shù)傳遞過去,那么 View 在收到數(shù)據(jù)后,不需要多余的邏輯操作,直接顯示就好了。那么 View(MainActivity)的代碼應(yīng)該做如下修改:
(4)View 層:
package com.test.mvp.mvpdemo.mvp.v1.view; import android.app.ProgressDialog; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.widget.TextView; import android.widget.Toast; import com.test.mvp.mvpdemo.R; import com.test.mvp.mvpdemo.mvp.v1.MainContract; import com.test.mvp.mvpdemo.mvp.v1.presenter.MainPresenter; /** * MVP 的寫法,Version 1: 基礎(chǔ)寫法 */ public class MainActivity extends AppCompatActivity implements MainContract.IMainView { private TextView tv; private MainPresenter mPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); mPresenter = new MainPresenter(this); mPresenter.handlerData(); } private void initViews() { tv = findViewById(R.id.tv); } @Override public void showDialog() { ProgressDialog dialog = new ProgressDialog(this); dialog.show(); new Handler().postDelayed(new Runnable() { @Override public void run() { dialog.dismiss(); } }, 1500); } @Override public void succes(String content) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "" + content, Toast.LENGTH_SHORT).show(); tv.setText(content); } }); } }
使用 MVP 架構(gòu),我們可以做到 Model 層和 Presenter 層的復(fù)用,如果不同的 Veiw 層需要相同的數(shù)據(jù),那么就無需修改 Model 層和 Presenter 層,直接實現(xiàn)接口就可以了。
到此為止,一個從網(wǎng)絡(luò)獲取數(shù)據(jù)并顯示的案例就被我們改寫為 MVP 架構(gòu)了,這是最基礎(chǔ)的 MVP 的入門版本,其中精要的就是interface 接口的使用,而接口的用法也是 Java 的基礎(chǔ),所以本篇文章內(nèi)容應(yīng)該不難理解。而 MVP 架構(gòu)也無需引入哪些庫、框架啊等等,它更是一種編程架構(gòu)、編程思想,并且現(xiàn)在也非常流行,所以我們一定要搞定它。
最后
如果你看到了這里,覺得文章寫得不錯就給個贊唄!歡迎大家評論討論!如果你覺得那里值得改進的,請給我留言。一定會認真查詢,修正不足,定期免費分享技術(shù)干貨。謝謝!