MVP 與 MVC 簡介
MVP 軟件架構在現在的應用中, 特別是 Android 端的編程中尤為突出的使用,因為 MVP 架構可以很深層次的去解耦視圖、業務邏輯、數據源三者的關系,讓它們之間的相互依賴性降低。MVP 是針對 Android 端開發而言的,它其實是 MVC 演變過來的,因為 MVC 模式在 Android 開發中并不是那么愉快。主要表現方面,比如,我們在寫一個功能模塊時,Activity 中的代碼很容易就突破到上千甚至上萬行,除了必要的 findviewid,listener 等,其他的代碼幾乎都是業務邏輯相關的,這就顯得 Activity 非常的臃腫,也不是說不可以,但是在模塊升級時造成業務邏輯的改變,我們就需要去成千上萬行的 Activity 中尋找業務邏輯代碼,這就有可能出現多處代碼需要修改,不細心的話,非常容易出 Bug,而且除了 Bug 代碼也不好定位。
第二個問題,數據源(來自數據庫或這網絡)部分可能會寫好幾份,比如,有兩個 Activity 同時有著個人信息數據要顯示,我必須在這兩個 Activity 中各請求一次網絡或者訪問數據庫,得到最新的數據,這就出現了代碼重復的問題。
帶著這兩個問題,我們來看看 MVP 架構能夠怎樣幫助我們解決 MVC 出現的這種問題!首先,我來簡單的介紹一下 MVC 它指的是:
- model(模型),負責訪問網絡數據、訪問數據庫數據,提供數據源
- view(視圖),負責更新界面、響應用戶界面操作
- controller(控制器),負責業務邏輯控制,處理數據
我們在 Android 開發中,Activity 負責 UI 顯示及更新操作,相當于 View 層,我們一般都會把代碼直接寫在 Activity 中,不管是數據源、邏輯控制、數據處理等操作一個勁的往 Activity 里寫,那么它們三者的關系都互相依賴,你中有我,我中有你,它們就形成了三角的關系。
這樣的關系,導致 Activity 代碼量過于龐大,修改和維護起來比較困難。這樣的三角戀關系,顯然不是長久之際。于是,MVP 它就強行把 View 與 Model 隔離了,讓它們不再有聯系,傳說中的 Presenter 就是腳踏兩只船,來回奔走于 View 與 Model 之間的契約者、聯系者。
MVP(model、view 、presenter),將 Activity 中的代碼抽離了出來,把 Activity(Fragment、View) 只當作 View 層,通過一個 Presenter(契約層)將 View 和 Model 層聯系起來,讓 View 和 Model 層充分的解耦。
我們訪問網絡得到數據并顯示出來會是這樣一個流程:
- Activity 啟動時,告訴 Presenter 我要數據了
- Presenter 就會叫 Model 去訪問數據接口得到數據
- Model 得到原數據就交給 Presenter 了,Presenter 一看數據不規范,趕緊處理一下,處理完成
- Presenter 就把處理后的數據匯報給 Activity(View),View 拿到數據就做顯示的操作,工作結束
MVC
下面我們通過一個簡單的案例來進行了解 MVP 架構的代碼寫法,當然,這里的寫法不唯一,因為每個人寫代碼的方式都是不一樣的,最核心的是 MVP 架構的思想,把思想轉為實際代碼,這才是值得我們探索和學習的地方。
來看看代碼,我們在網絡請求時,獲取到數據,并 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); } }); } }
它的效果其實就是請求百度首頁,返回網頁數據以 string 方式顯示出來而已。
上面的代碼,其實就是 MVC 架構的寫法,把 Model(網頁數據)、Presenter(數據處理)、View(activity)三層寫在了一起,雖然沒什么問題,但我們看看 MainActivity 中的代碼比較雜亂,在業務代碼比較多的情況下,MainActivity 更加的沉重,不利于維護與業務升級。
MVP 實戰
那么我們下面就要將這個類中的代碼改寫為 MVP 的寫法,回顧上面提及的 MVP 架構的思想,它是將 View 層與 Model 層徹底隔離,意味著 View 和 Model 都不再持對方的引用,它們通過一個第三者 Presenter 來代理事物的傳遞,所以 Presenter 層會持有 Model 與 View 層的引用,這是第一步。
第二步,是將它們之間的聯系抽象出來,以接口的方式相互調用,所以 Model 、View、Presenter 各自擁有自己的接口和抽象方法,所以這就會無形的多出了三個接口類,這也就是 MVP 的缺點之一。所以,為了較少的創建接口類,我們就給這三層接口定義了一個契約接口,把它們更加緊密的結合在一起,方法查看,例如代碼這樣寫:
(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 分包,分別創建 Model 層實現類、Presenter 層實現類、MainActivity 就相當于View 層,這樣一來架構就更加清晰明了:
接著,分別給這三層實現我們剛剛寫的 MainContract 中相對應的接口,我們先來看看 Model 層,它就主要負責網絡請求,也就是我們的 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 層,請求網絡或數據庫,提供數據源(原始數據) */ 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 層中處理業務數據,所以我們讓它自己回調給 Presenter 層來處理,保證數據的原始狀態。
(3)Presenter 層:
然后是 Presenter 層,它是處理業務邏輯和業務數據的,所以必須持有 Model 的引用,同時要將處理完的數據交給 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 層,承擔業務邏輯處理,數據源處理等 */ 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(); } /** * 發起請求,獲得回調數據 */ 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); } } }); } }
這里的處理數據我就不做說明了,注意一點,這里是請求網絡數據會有延時,我們 Presenter 層持有了 View 層的引用,也就是 Activity 的引用,在網絡堵塞的情況下,用戶在打開 Activity 又馬上給關閉了,這時 View 相當于被摧毀了,如果不進行判斷 View 引用是否為空,當數據在延遲幾秒后返回時,就會造成空指針異常,所以每次都要進行判斷才合理。
最后,在 Presenter 層處理完了數據,就要將數據傳達給 View 層顯示,所以要調用 View 接口的抽象方法,把數據參數傳遞過去,那么 View 在收到數據后,不需要多余的邏輯操作,直接顯示就好了。那么 View(MainActivity)的代碼應該做如下修改:
(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: 基礎寫法 */ 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 架構,我們可以做到 Model 層和 Presenter 層的復用,如果不同的 Veiw 層需要相同的數據,那么就無需修改 Model 層和 Presenter 層,直接實現接口就可以了。
到此為止,一個從網絡獲取數據并顯示的案例就被我們改寫為 MVP 架構了,這是最基礎的 MVP 的入門版本,其中精要的就是interface 接口的使用,而接口的用法也是 Java 的基礎,所以本篇文章內容應該不難理解。而 MVP 架構也無需引入哪些庫、框架啊等等,它更是一種編程架構、編程思想,并且現在也非常流行,所以我們一定要搞定它。
最后
如果你看到了這里,覺得文章寫得不錯就給個贊唄!歡迎大家評論討論!如果你覺得那里值得改進的,請給我留言。一定會認真查詢,修正不足,定期免費分享技術干貨。謝謝!