1. 程式人生 > >MVP 模式簡單易懂的介紹方式

MVP 模式簡單易懂的介紹方式

之一 經驗 main 不同 nds mvc 是把 get 意圖

為什麽用Android MVP 設計模式? 當項目越來越龐大、復雜,參與的研發人員越來越多的時候,MVP 模式 的優勢就充分顯示出來了。

MVP 模式是 MVC 模式在 Android 上的一種變體,要介紹 MVP 就得先介紹 MVC。在 MVC 模式中,Activity 應該是屬於 View 這一層。而實質上,它既承擔了 View,同時也包含一些 Controller 的東西在裏面。這對於開發與維護來說不太友好,耦合度大高了。把 Activity 的 View 和 Controller 抽離出來就變成了 View 和 Presenter,這就是 MVP 模式.。

而MVC模式。大家可能比較熟悉,就算不熟悉也可能或多或少地在自己的項目中用到過。要介紹 MVP 模式,就不得不先說說 MVC 模式。

MVC 模式

MVC 模式的結構分為三部分,實體層的 Model,視圖層的 View,以及控制層的 Controller。

技術分享

  • 其中 View 層其實就是程序的 UI 界面,用於向用戶展示數據以及接收用戶的輸入
  • 而 Model 層就是 JavaBean 實體類,用於保存實例數據
  • Controller 控制器用於更新 UI 界面和數據實例

例如,View 層接受用戶的輸入,然後通過 Controller 修改對應的 Model 實例;同時,當 Model 實例的數據發生變化的時候,需要修改 UI 界面,可以通過 Controller 更新界面。(View 層也可以直接更新 Model 實例的數據,而不用每次都通過 Controller,這樣對於一些簡單的數據更新工作會變得方便許多。)

舉個簡單的例子,現在要實現一個飄雪的動態壁紙,可以給雪花定義一個實體類 Snow,裏面存放 XY 軸坐標數據,View 層當然就是 SurfaceView(或者其他視圖),為了實現雪花飄的效果,可以啟動一個後臺線程,在線程裏不斷更新 Snow 實例裏的坐標值,這部分就是 Controller 的工作了,Controller 裏還要定時更新 SurfaceView 上面的雪花。進一步的話,可以在 SurfaceView 上監聽用戶的點擊,如果用戶點擊,只通過 Controller 對觸摸點周圍的 Snow 的坐標值進行調整,從而實現雪花在用戶點擊後出現彈開等效果。具體的 MVC 模式請自行 Google。

MVP 模式

在 Android 項目中,Activity 和 Fragment 占據了大部分的開發工作。如果有一種設計模式(或者說代碼結構)專門是為優化 Activity 和 Fragment 的代碼而產生的,你說這種模式重要不?這就是 MVP 設計模式。

按照 MVC 的分層,Activity 和 Fragment(後面只說 Activity)應該屬於 View 層,用於展示 UI 界面,以及接收用戶的輸入,此外還要承擔一些生命周期的工作。Activity 是在 Android 開發中充當非常重要的角色,特別是 TA 的生命周期的功能,所以開發的時候我們經常把一些業務邏輯直接寫在 Activity 裏面,這非常直觀方便,代價就是 Activity 會越來越臃腫,超過 1000 行代碼是常有的事,而且如果是一些可以通用的業務邏輯(比如用戶登錄),寫在具體的 Activity 裏就意味著這個邏輯不能復用了。如果有進行代碼重構經驗的人,看到 1000 + 行的類肯定會有所顧慮。因此,Activity 不僅承擔了 View 的角色,還承擔了一部分的 Controller 角色,這樣一來 V 和 C 就耦合在一起了,雖然這樣寫方便,但是如果業務調整的話,要維護起來就難了,而且在一個臃腫的 Activity 類查找業務邏輯的代碼也會非常蛋疼,所以看起來有必要在 Activity 中,把 View 和 Controller 抽離開來,而這就是 MVP 模式的工作了。

技術分享

MVP 模式的核心思想

MVP 把 Activity 中的 UI 邏輯抽象成 View 接口,把業務邏輯抽象成 Presenter 接口,Model 類還是原來的 Model

這就是 MVP 模式,現在這樣的話,Activity 的工作的簡單了,只用來響應生命周期,其他工作都丟到 Presenter 中去完成。從上圖可以看出,Presenter 是 Model 和 View 之間的橋梁,為了讓結構變得更加簡單,View 並不能直接對 Model 進行操作,這也是 MVP 與 MVC 最大的不同之處。

MVP 模式的作用

MVP 的好處都有啥,誰說對了就給他 KIRA!!(<ゝω·)☆

  • 分離了視圖邏輯和業務邏輯,降低了耦合
  • Activity 只處理生命周期的任務,代碼變得更加簡潔
  • 視圖邏輯和業務邏輯分別抽象到了 View 和 Presenter 的接口中去,提高代碼的可閱讀性
  • Presenter 被抽象成接口,可以有多種具體的實現,所以方便進行單元測試
  • 把業務邏輯抽到 Presenter 中去,避免後臺線程引用著 Activity 導致 Activity 的資源無法被系統回收從而引起內存泄露和 OOM

其中最重要的有三點

Activity 代碼變得更加簡潔

相信很多人閱讀代碼的時候,都是從 Activity 開始的,對著一個 1000 + 行代碼的 Activity,看了都覺得難受。

使用 MVP 之後,Activity 就能瘦身許多了,基本上只有 FindView、SetListener 以及 Init 的代碼。其他的就是對 Presenter 的調用,還有對 View 接口的實現。這種情形下閱讀代碼就容易多了,而且你只要看 Presenter 的接口,就能明白這個模塊都有哪些業務,很快就能定位到具體代碼。Activity 變得容易看懂,容易維護,以後要調整業務、刪減功能也就變得簡單許多。

方便進行單元測試

一般單元測試都是用來測試某些新加的業務邏輯有沒有問題,如果采用傳統的代碼風格(習慣性上叫做 MV 模式,少了 P),我們可能要先在 Activity 裏寫一段測試代碼,測試完了再把測試代碼刪掉換成正式代碼,這時如果發現業務有問題又得換回測試代碼,咦,測試代碼已經刪掉了!好吧重新寫吧……

MVP 中,由於業務邏輯都在 Presenter 裏,我們完全可以寫一個 PresenterTest 的實現類繼承 Presenter 的接口,現在只要在 Activity 裏把 Presenter 的創建換成 PresenterTest,就能進行單元測試了,測試完再換回來即可。萬一發現還得進行測試,那就再換成 PresenterTest 吧。

避免 Activity 的內存泄露

Android APP 發生 OOM 的最大原因就是出現內存泄露造成 APP 的內存不夠用,而造成內存泄露的兩大原因之一就是 Activity 泄露(Activity Leak)(另一個原因是 Bitmap 泄露(Bitmap Leak))。

Java 一個強大的功能就是其虛擬機的內存回收機制,這個功能使得 Java 用戶在設計代碼的時候,不用像 C++ 用戶那樣考慮對象的回收問題。然而,Java 用戶總是喜歡隨便寫一大堆對象,然後幻想著虛擬機能幫他們處理好內存的回收工作。可是虛擬機在回收內存的時候,只會回收那些沒有被引用的對象,被引用著的對象因為還可能會被調用,所以不能回收。

Activity 是有生命周期的,用戶隨時可能切換 Activity,當 APP 的內存不夠用的時候,系統會回收處於後臺的 Activity 的資源以避免 OOM。

采用傳統的 MV 模式,一大堆異步任務和對 UI 的操作都放在 Activity 裏面,比如你可能從網絡下載一張圖片,在下載成功的回調裏把圖片加載到 Activity 的 ImageView 裏面,所以異步任務保留著對 Activity 的引用。這樣一來,即使 Activity 已經被切換到後臺(onDestroy 已經執行),這些異步任務仍然保留著對 Activity 實例的引用,所以系統就無法回收這個 Activity 實例了,結果就是 Activity Leak。Android 的組件中,Activity 對象往往是在堆(Java Heap)裏占最多內存的,所以系統會優先回收 Activity 對象,如果有 Activity Leak,APP 很容易因為內存不夠而 OOM。

采用 MVP 模式,只要在當前的 Activity 的 onDestroy 裏,分離異步任務對 Activity 的引用,就能避免 Activity Leak。

說了這麽多,沒看懂?好吧,我自己都沒看懂自己寫的,我們還是直接看代碼吧。

MVP 模式的使用

技術分享

上面一張簡單的 MVP 模式的 UML 圖,從圖中可以看出,使用 MVP,至少需要經歷以下步驟:

  1. 創建 IPresenter 接口,把所有業務邏輯的接口都放在這裏,並創建它的實現 PresenterCompl(在這裏可以方便地查看業務功能,由於接口可以有多種實現所以也方便寫單元測試)
  2. 創建 IView 接口,把所有視圖邏輯的接口都放在這裏,其實現類是當前的 Activity/Fragment
  3. 由 UML 圖可以看出,Activity 裏包含了一個 IPresenter,而 PresenterCompl 裏又包含了一個 IView 並且依賴了 Model。Activity 裏只保留對 IPresenter 的調用,其它工作全部留到 PresenterCompl 中實現
  4. Model 並不是必須有的,但是一定會有 View 和 Presenter

通過上面的介紹,MVP 的主要特點就是把 Activity 裏的許多邏輯都抽離到 View 和 Presenter 接口中去,並由具體的實現類來完成。這種寫法多了許多 IView 和 IPresenter 的接口,在某種程度上加大了開發的工作量,剛開始使用 MVP 的小夥伴可能會覺得這種寫法比較別扭,而且難以記住。其實一開始想太多也沒有什麽卵用,只要在具體項目中多寫幾次,就能熟悉 MVP 模式的寫法,理解 TA 的意圖,以及享♂受其帶來的好處。

扯了這麽多,但是好像並沒有什麽卵用,畢竟

Talk is cheap, let me show you the code!

所以還是來寫一下實際的項目吧。

MVP 模式簡單實例

技術分享

一個簡單的登錄界面(實在想不到別的了╮( ̄▽ ̄”)╭),點擊 LOGIN 則進行賬號密碼驗證,點擊 CLEAR 則重置輸入。

技術分享

項目結構看起來像是這個樣子的,MVP 的分層還是很清晰的。我的習慣是先按模塊分 Package,在模塊下面再去創建 model、view、presenter 的子 Package,當然也可以用 model、view、presenter 作為頂級的 Package,然後把所有的模塊的 model、view、presenter 類都到這三個頂級 Package 中,就好像有人喜歡把項目裏所有的 Activity、Fragment、Adapter 都放在一起一樣。

首先來看看 LoginActivity:

 1 package huolongluo.nodata.login;
 2 
 3 import android.os.Bundle;
 4 import android.support.v7.app.AppCompatActivity;
 5 import android.text.TextUtils;
 6 import android.view.View;
 7 import android.widget.Button;
 8 import android.widget.EditText;
 9 import android.widget.ProgressBar;
10 import android.widget.Toast;
11 
12 import butterknife.BindView;
13 import butterknife.ButterKnife;
14 import butterknife.OnClick;
15 import huolongluo.nodata.R;
16 
17 public class LoginActivity extends AppCompatActivity implements ILoginView
18 {
19     @BindView(R.id.pb_login)
20     ProgressBar pb_login;
21 
22     @BindView(R.id.et_username)
23     EditText et_username;
24     @BindView(R.id.et_password)
25     EditText et_password;
26 
27     @BindView(R.id.btn_login)
28     Button btn_login;
29     @BindView(R.id.btn_cancel)
30     Button btn_cancel;
31 
32 
33     private LoginPresenterCompl loginPresenterCompl;
34 
35     @Override
36     protected void onCreate(Bundle savedInstanceState)
37     {
38         super.onCreate(savedInstanceState);
39         setContentView(R.layout.activity_main);
40         ButterKnife.bind(this);
41         loginPresenterCompl = new LoginPresenterCompl(this);
42     }
43 
44     @OnClick({R.id.btn_login, R.id.btn_cancel})
45     public void onClick(View view)
46     {
47         switch (view.getId())
48         {
49             case R.id.btn_login:
50                 loginPresenterCompl.setProgressBarVisiblity(View.VISIBLE);
51 
52                 String name = et_username.getText().toString().trim();
53                 String password = et_password.getText().toString().trim();
54                 loginPresenterCompl.doLogin(name, password);
55                 break;
56             case R.id.btn_cancel:
57                 loginPresenterCompl.clear();
58                 break;
59         }
60     }
61 
62     @Override
63     public void onClearEdit()
64     {
65         et_username.setText("");
66         et_password.setText("");
67     }
68 
69     @Override
70     public void onRequestLogin(String name, String pass)
71     {
72         loginPresenterCompl.setProgressBarVisiblity(View.GONE);
73 
74         if (TextUtils.equals(name, "123") && TextUtils.equals(pass, "123"))
75         {
76             Toast.makeText(this, "登陸成功", Toast.LENGTH_SHORT).show();
77         }
78         else
79         {
80             Toast.makeText(this, "登陸失敗", Toast.LENGTH_SHORT).show();
81         }
82     }
83 
84     @Override
85     public void onSetProgressBarVisibility(int visibility)
86     {
87         pb_login.setVisibility(visibility);
88     }
89 }

從代碼可以看出 LoginActivity 只做了 findView 以及 setListener 的工作,而且包含了一個 ILoginPresenter,所有業務邏輯都是通過調用 ILoginPresenter 的具體接口來完成。所以 LoginActivity 的代碼看起來很舒爽,甚至有點愉♂悅呢 (/ω\*)。視力不錯的你可能還看到了 ILoginView 接口的實現,如果不懂為什麽要這樣寫的話,可以先往下看,這裏只要記住 “LoginActivity 實現了 ILoginView 接口”。

再來看看 ILoginPresenter:

 1 package huolongluo.nodata.login;
 2 
 3 /**
 4  * <p>
 5  * Created by 火龍裸 on 2017/9/30 0030.
 6  */
 7 
 8 public interface ILoginPresenter
 9 {
10     void clear(); // 清空編輯框數據
11     
12     void doLogin(String userName, String passWord); // 請求登陸
13     
14     void setProgressBarVisiblity(int visiblity); // 設置進度條的顯示和隱藏
15 }

LoginPresenterCompl.java:
 1 package huolongluo.nodata.login;
 2 
 3 import android.os.Looper;
 4 
 5 /**
 6  * <p>
 7  * Created by 火龍裸 on 2017/9/30 0030.
 8  */
 9 
10 public class LoginPresenterCompl implements ILoginPresenter
11 {
12     private ILoginView iLoginView;
13     private android.os.Handler handler;
14 
15     public LoginPresenterCompl(ILoginView iLoginView)
16     {
17         this.iLoginView = iLoginView;
18         handler = new android.os.Handler(Looper.getMainLooper());
19     }
20 
21     @Override
22     public void clear()
23     {
24         iLoginView.onClearEdit();
25     }
26 
27     @Override
28     public void doLogin(final String userName, final String passWord)
29     {
30         handler.postDelayed(new Runnable()
31         {
32             @Override
33             public void run()
34             {
35                 iLoginView.onRequestLogin(userName, passWord);
36             }
37         }, 2000);
38         
39     }
40 
41     @Override
42     public void setProgressBarVisiblity(int visiblity)
43     {
44         iLoginView.onSetProgressBarVisibility(visiblity);
45     }
46 }

從代碼可以看出,LoginPresenterCompl 保留了 ILoginView 的引用,因此在 LoginPresenterCompl 裏就可以直接進行 UI 操作了,而不用在 Activity 裏完成。這裏使用了 ILoginView 引用,而不是直接使用 Activity,這樣一來,如果在別的 Activity 裏也需要用到相同的業務邏輯,就可以直接復用 LoginPresenterCompl 類了(一個 Activity 可以包含一個以上的 Presenter,總之,需要什麽業務就 new 什麽樣的 Presenter,是不是很靈活(@ ̄︶ ̄@)),這也是 MVP 的核心思想

通過 IVIew 和 IPresenter,把 Activity 的UI LogicBusiness Logic分離開來,Activity just does its basic job! 至於 Model 嘛,還是原來 MVC 裏的 Model。

再來看看 ILoginView,至於 ILoginView 的實現類呢,翻到上面看看 LoginActivity 吧

 1 package huolongluo.nodata.login;
 2 
 3 /**
 4  * <p>
 5  * Created by 火龍裸 on 2017/9/30 0030.
 6  */
 7 
 8 public interface ILoginView
 9 {
10     void onClearEdit();
11     
12     void onRequestLogin(String name, String pass);
13     
14     void onSetProgressBarVisibility(int visibility);
15 }

代碼這種東西放在日誌裏講好像除了把整個版面拉長沒什麽卵用,MVP又很多種寫法,其實很容易發現,其中“ILoginView” 和 “ILoginPresenter” 這兩個接口,一個用來調用,一個業務邏輯方法的調用,一個用來處理業務結果的。通過自己的理解,有自己的一套寫法,類似於這種寫法:

技術分享

已經對其封裝了成一個萬能的Android項目框架。以後有什麽項目需要,直接可以down下來用就是了。這個框架用到了Rxjava+Retrofit+Okhttp3+Dagger2+EventBus+Lambda插件, 感興趣的童鞋,要是覺得寫得不錯,歡迎給個star ╮( ̄▽ ̄”)╭ https://github.com/huolongluo/Sample 。

MVP 模式簡單易懂的介紹方式