優化你的代碼結構 --- MVP

安卓基礎開發庫,讓開發簡單點。
Demo地址https://github.com/LJYcoder/MvpDagger

學習/參考地址:
http://www.lxweimin.com/p/91c2bb8e6369
http://www.lxweimin.com/p/9d40b298eca9
http://blog.csdn.net/lmj623565791/article/details/46596109

MVP是什么?

MVP指安卓中的一種開發模式。
它將代碼整體分為M(Model)、V(View)、P(Presenter)三層。

正經版:
M層(model):數據模型/處理層。負責數據處理、數據提供,如網絡請求,數據庫操作
V層(view):視圖展示層。負責界面展示,如Activity,Fragment
P層(presenter):業務邏輯層。負責業務邏輯服務,是V層與M層間的橋梁

MVP示意圖1

你也可以這樣幫助理解下(餐廳版):
M層(model):廚師。負責做菜
V層(view):顧客。點餐吃飯
P層(presenter):服務員。提供下單、上菜等各種服務

MVP示意圖2

與MVC模式的區別:
MVC中,V層與M層是可以互通的,而在MVP中V層與M層是不通的。
按餐廳版來說就是,MVC中顧客可以直接告訴廚師要吃什么菜,廚師做好后直接把菜端到你面前,而MVP中只能通過服務員來完成點餐到用餐的過程。

使用MVP有什么好處?

抽象些來說:
MVP可以降低代碼耦合度,提高代碼的結構清晰度、可讀性、維護性和復用性。

具體些來說(參考JessYan的例子):
現在有這么一個需求:Activity中從網絡獲取數據然后展示在A控件上。
如果不用MVP的話,那就直接把獲取展示等代碼都寫在Activity中,很快便可以寫完。

但現在需求變動了:
1.要求加入緩存功能,如果本地有數據,則先從本地獲取數據,然后再從網絡獲取最新數據進行替換
2.要求數據展示在B控件上而不是A控件。

如果代碼都是你自己寫的,那改起來還比較輕松,但假如是團隊開發,代碼不是你寫的,你需要花時間把邏輯重新看一遍再開始改,而且如果改錯的話,會影響之前已經寫好的功能。

但使用MVP模式進行開發就不同了。由于它的分工結構清晰,V層僅負責數據展示,P層僅負責業務邏輯,M層僅負責數據獲取/處理。所以改動起來就輕松很多。
對于變動的需求1:我們只需在P層加入邏輯判斷(先從本地獲取,再網絡獲取),然后M層增加一個從本地獲取數據方法。
對于變動的需求2:我們只需在V層修改獲取到數據后的展示方式,從控件A改成控件B。

當然還有可復用等優點,這里就不具體講了。
至于"缺點"嘛,就是會相應地增加代碼量。有得有失,但得大于失


具體使用

以這么一個場景為例:
從網絡獲取“正在上映”的電影數據,獲取成功則將數據在頁面展示,獲取失敗則給出相應提示。

需要寫四個部分:Model層,View層,Presenter層,接口

接口

負責“連接”MVP三層,以便方法調用、數據流動。同時也便于進行單元測試。

IView

View層接口,定義View層需實現的方法,P層通過該接口回調通知View層。

public interface IMovieView {
    //成功獲取到電影數據
    void getMovieSuccess(List<MovieRes> list, int type);
    //獲取電影數據失敗
    void getMovieFail(int status, String desc, int type);
}

IModel

Model層接口,定義Model層需實現的方法,P層通過該接口調用M層獲取/處理數據的方法。

public interface IMovieMoel{
    //請求正在上映的電影數據
    Observable getPlayingMovie(int start, int count);
    ...
}

Model層

實現IModel接口中的方法,負責數據的獲取/處理。

public class MovieModel implements IMovieMoel{

    @Override
    public Observable getPlayingMovie(int start,int count) {
        //提供數據源
        return DevRing.httpManager().getService(MovieApiService.class).getPlayingMovie(start, count);
    }
    ...
}

Presenter層

處理業務邏輯,調用M層獲取數據,調用V層傳遞展示數據。

public class MoviePresenter {
    private IMovieView mIView;
    private IMovieModel mIModel;

    public MoviePresenter(IMovieView iMovieView, IMovieMoel iMovieMoel) {
        mIView = iMovieView;
        mIModel = iMovieModel;
    }

    /**
     * 獲取正在上映的電影
     *
     * @param start 請求電影的起始位置
     * @param count 獲取的電影數量
     * @param type  類型:初始化數據INIT、刷新數據REFRESH、加載更多數據LOADMORE
     */
    public void getPlayingMovie(int start, int count, final int type) {

        DevRing.httpManager().commonRequest( mIModel.getPlayingMovie(start, count),
         new CommonObserver<HttpResult<List<MovieRes>>>() {
            @Override
            public void onResult(HttpResult<List<MovieRes>> result) {
                if (mIView != null) {
                    mIView.getMovieSuccess(result.getSubjects(), type);
                }
            }

            @Override
            public void onError(int errType, String errMessage) {
                if (mIView != null) {
                    mIView.getMovieFail(errType, errMessage, type);
                }
            }
        }, RxLifecycleUtil.bindUntilDestroy(mIView));
    }

    ...

     /**
     * 釋放引用,防止內存泄露
     */
    public void destroy() {
        mIView = null;
    }
}

View層

實現IView接口中的方法,對獲取到的數據進行展示

public class MovieActivity extends Activity implements IMovieView {
    //獲取電影數據成功的網絡請求回調
    @Override
    public void getMovieSuccess(List<MovieRes> list, int type) {
        //成功,對數據進行展示
        ....
    }

    //獲取電影數據失敗的網絡請求回調
    @Override
    public void getMovieFail(int status, String desc, int type) {
        //失敗,界面上做出相應提示
        ...
    }

    ...
}

完成以上幾步后,在View層初始化時,調用Presenter層方法即可。

@Override
protected void onCreate(Bundle saveInstanceState) {
      ...
      mPresenter = new MoviePresenter(this, new MovieModel());
      mPresenter.getPlayingMovie(start, mCount, type);
}

還有一點需注意:
如果Presenter層持有了View層的引用,那么記得在V層銷毀時,把Presenter層中對View層的引用置null,避免View層回收失敗導致內存泄漏。

@Override
public void onDestroy() {
     super.onDestroy();
     if (mPresenter != null) {
          mPresenter.destroy();
          mPresenter = null;
     }
 }

2018.4.13:
github的MvpDagger項目中新增了MVP一鍵生成模板,根據Demo的代碼結構定制的,有需要的可以查看。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,301評論 25 708
  • 前言 看了下上篇博客的發表時間到這篇博客,竟然過了11個月,罪過,罪過。這一年時間也是夠折騰的,年初離職跳槽到鵝廠...
    西木柚子閱讀 21,292評論 12 184
  • 一直很糾結,這個Parse.Cloud到底是什么鬼?后來,經過簡單代碼閱讀與實際編寫實踐,發現,挺有意思! 簡單的...
    NextStack閱讀 832評論 0 0
  • 上學期間勤工儉學時,我去過韓城。那里的農村,有些村莊的人家住的很分散,馬路邊上靠近小山坡的向陽位置是最受歡迎的,三...
    桂丑閱讀 237評論 7 13
  • 我們經常看到有人一直很努力,卻是一事無成,是什么原因影響的呢?姑且不論方法和方向有沒有問題,卻總能看到Ta同時想兼...
    Louise718閱讀 161評論 2 5