1. 程式人生 > >淺談 MVP in Android

淺談 MVP in Android

                     

一、概述

對於MVP(Model View Presenter),大多數人都能說出一二:“MVC的演化版本”,“讓Model和View完全解耦”等等。本篇博文僅是為了做下記錄,提出一些自己的看法,和幫助大家如何針對一個Activity頁面去編寫針對MVP風格的程式碼。

對於MVP,我的內心有一個問題:

 

為何這個模式出來後,就能被廣大的Android的程式設計師接受呢?

問了些程式設計師,他們對於MVP的普遍的認識是:“程式碼很清晰,不過增加了很多類”。我在第一次看到MVP的時候,看了一個demo,看完以後覺得非常nice(但是回過頭來,自己想個例子寫,就頭疼寫不出來,當然這在後文會說)。nice的原因還是因為,這個模式的確讓程式碼的清晰度有了很大的提升。

那麼,提升一般都是對比出來的,回顧下,沒有應用MVP的程式碼結構。很多人說明顯是MVC麼:

  • View:對應於佈局檔案
  • Model:業務邏輯和實體模型
  • Controllor:對應於Activity

看起來的確像那麼回事,但是細細的想想這個View對應於佈局檔案,其實能做的事情特別少,實際上關於該佈局檔案中的資料繫結的操作,事件處理的程式碼都在Activity中,造成了Activity既像View又像Controller(當然了Data-Binder的出現,可能會讓View更像View吧)。這可能也就是為何,在該文中有一句這樣的話:

 

Most of the modern Android applications just use View-Model architecture,everything is connected with Activity.

而當將架構改為MVP以後,Presenter的出現,將Actvity視為View層,Presenter負責完成View層與Model層的互動。現在是這樣的:

  • View 對應於Activity,負責View的繪製以及與使用者互動
  • Model 依然是業務邏輯和實體模型
  • Presenter 負責完成View於Model間的互動

ok,先簡單瞭解下,文中會有例子到時候可以直觀的感受下。

小總結下,也就是說,之所以讓人覺得耳目一新,是因為這次的跳躍是從並不標準的MVCMVP的一個轉變,減少了Activity的職責,簡化了Activity中的程式碼,將複雜的邏輯程式碼提取到了Presenter中進行處理。與之對應的好處就是,耦合度更低,更方便的進行測試。借用兩張圖(出自:

該文),代表上述的轉變:

轉變為:

 

二、MVP 與 MVC 區別

ok,上面說了一堆理論,下面我們還是需要看一看MVC與MVP的一個區別,請看下圖(來自:本文):

其實最明顯的區別就是,MVC中是允許Model和View進行互動的,而MVP中很明顯,Model與View之間的互動由Presenter完成。還有一點就是Presenter與View之間的互動是通過介面的(程式碼中會體現)。

還有一堆概念性的東西,以及優點就略了,有興趣自行百度。下面還是通過一些簡單的需求來展示如何編寫MVP的demo。

三、Simple Login Demo

效果圖:

看到這樣的效果,先看下完工後的專案結構:

ok,接下來開始一步一步的編寫思路。

(一)Model

首先實體類User不用考慮這個肯定有,其次從效果圖可以看到至少有一個業務方法login(),這兩點沒什麼難度,我們首先完成:

package com.zhy.blogcodes.mvp.bean;/** * Created by zhy on 15/6/18. */public class User{    private String username ;    private String password ;    public String getUsername()    {        return username;    }    public void setUsername(String username)    {        this.username = username;    }    public String getPassword()    {        return password;    }    public void setPassword(String password)    {        this.password = password;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
package com.zhy.blogcodes.mvp.biz;/** * Created by zhy on 15/6/19. */public interface IUserBiz{    public void login(String username, String password, OnLoginListener loginListener);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
package com.zhy.blogcodes.mvp.biz;import com.zhy.blogcodes.mvp.bean.User;/** * Created by zhy on 15/6/19. */public class UserBiz implements IUserBiz{    @Override    public void login(final String username, final String password, final OnLoginListener loginListener)    {        //模擬子執行緒耗時操作        new Thread()        {            @Override            public void run()            {                try                {                    Thread.sleep(2000);                } catch (InterruptedException e)                {                    e.printStackTrace();                }                //模擬登入成功                if ("zhy".equals(username) && "123".equals(password))                {                    User user = new User();                    user.setUsername(username);                    user.setPassword(password);                    loginListener.loginSuccess(user);                } else                {                    loginListener.loginFailed();                }            }        }.start();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
package com.zhy.blogcodes.mvp.biz;import com.zhy.blogcodes.mvp.bean.User;/** * Created by zhy on 15/6/19. */public interface OnLoginListener{    void loginSuccess(User user);    void loginFailed();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

實體類不用說,至於業務類,我們抽取了一個介面,一個實現類這也很常見~~login方法,一般肯定是連線伺服器的,是個耗時操作,所以我們開闢了子執行緒,Thread.sleep(2000)模擬了耗時,由於是耗時操作,所以我們通過一個回撥介面來通知登入的狀態。

其實這裡還是比較好寫的,因為和傳統寫法沒區別。

(二) View

上面我們說過,Presenter與View互動是通過介面。所以我們這裡需要定義一個ILoginView,難點就在於應該有哪些方法,我們看一眼效果圖:

可以看到我們有兩個按鈕,一個是login,一個是clear;

login說明了要有使用者名稱、密碼,那麼對應兩個方法:

    String getUserName();    String getPassword();
  • 1
  • 2
  • 3
  • 4

再者login是個耗時操作,我們需要給使用者一個友好的提示,一般就是操作ProgressBar,所以再兩個:

    void showLoading();    void hideLoading();
  • 1
  • 2
  • 3

login當然存在登入成功與失敗的處理,我們主要看成功我們是跳轉Activity,而失敗可能是去給個提醒:

    void toMainActivity(User user);    void showFailedError();
  • 1
  • 2
  • 3

ok,login這個方法我們分析完了~~還剩個clear那就簡單了:

    void clearUserName();    void clearPassword();
  • 1
  • 2
  • 3

綜上,介面完整為:

package com.zhy.blogcodes.mvp.view;import com.zhy.blogcodes.mvp.bean.User;/** * Created by zhy on 15/6/19. */public interface IUserLoginView{    String getUserName();    String getPassword();    void clearUserName();    void clearPassword();    void showLoading();    void hideLoading();    void toMainActivity(User user);    void showFailedError();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

有了介面,實現就太好寫了~~~

總結下,對於View的介面,去觀察功能上的操作,然後考慮:

  • 該操作需要什麼?(getUserName, getPassword)
  • 該操作的結果,對應的反饋?(toMainActivity, showFailedError)
  • 該操作過程中對應的友好的互動?(showLoading, hideLoading)

下面貼一下我們的View的實現類,哈,其實就是Activity,文章開始就說過,MVP中的View其實就是Activity。

package com.zhy.blogcodes.mvp;import android.os.Bundle;import android.support.v7.app.ActionBarActivity;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.ProgressBar;import android.widget.Toast;import com.zhy.blogcodes.R;import com.zhy.blogcodes.mvp.bean.User;import com.zhy.blogcodes.mvp.presenter.UserLoginPresenter;import com.zhy.blogcodes.mvp.view.IUserLoginView;public class UserLoginActivity extends ActionBarActivity implements IUserLoginView{    private EditText mEtUsername, mEtPassword;    private Button mBtnLogin, mBtnClear;    private ProgressBar mPbLoading;    private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this);    @Override    protected void onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_user_login);        initViews();    }    private void initViews()    {        mEtUsername = (EditText) findViewById(R.id.id_et_username);        mEtPassword = (EditText) findViewById(R.id.id_et_password);        mBtnClear = (Button) findViewById(R.id.id_btn_clear);        mBtnLogin = (Button) findViewById(R.id.id_btn_login);        mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading);        mBtnLogin.setOnClickListener(new View.OnClickListener()        {            @Override            public void onClick(View v)            {                mUserLoginPresenter.login();            }        });        mBtnClear.setOnClickListener(new View.OnClickListener()        {            @Override            public void onClick(View v)            {                mUserLoginPresenter.clear();            }        });    }    @Override    public String getUserName()    {        return mEtUsername.getText().toString();    }    @Override    public String getPassword()    {        return mEtPassword.getText().toString();    }    @Override    public void clearUserName()    {        mEtUsername.setText("");    }    @Override    public void clearPassword()    {        mEtPassword.setText("");    }    @Override    public void showLoading()    {        mPbLoading.setVisibility(View.VISIBLE);    }    @Override    public void hideLoading()    {        mPbLoading.setVisibility(View.GONE);    }    @Override    public void toMainActivity(User user)    {        Toast.makeText(this, user.getUsername() +                " login success , to MainActivity", Toast.LENGTH_SHORT).show();    }    @Override    public void showFailedError()    {        Toast.makeText(this,                "login failed", Toast.LENGTH_SHORT).show();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

對於在Activity中實現我們上述定義的介面,是一件很容易的事,畢竟介面引導我們去完成。

最後看我們的Presenter。

(三)Presenter

Presenter是用作Model和View之間互動的橋樑,那麼應該有什麼方法呢?

其實也是主要看該功能有什麼操作,比如本例,兩個操作:login和clear。

package com.zhy.blogcodes.mvp.presenter;import android.os.Handler;import com.zhy.blogcodes.mvp.bean.User;import com.zhy.blogcodes.mvp.biz.IUserBiz;import com.zhy.blogcodes.mvp.biz.OnLoginListener;import com.zhy.blogcodes.mvp.biz.UserBiz;import com.zhy.blogcodes.mvp.view.IUserLoginView;/** * Created by zhy on 15/6/19. */public class UserLoginPresenter{    private IUserBiz userBiz;    private IUserLoginView userLoginView;    private Handler mHandler = new Handler();    public UserLoginPresenter(IUserLoginView userLoginView)    {        this.userLoginView = userLoginView;        this.userBiz = new UserBiz();    }    public void login()    {        userLoginView.showLoading();        userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener()        {            @Override            public void loginSuccess(final User user)            {                //需要在UI執行緒執行                mHandler.post(new Runnable()                {                    @Override                    public void run()                    {                        userLoginView.toMainActivity(user);                        userLoginView.hideLoading();                    }                });            }            @Override            public void loginFailed()            {                //需要在UI執行緒執行                mHandler.post(new Runnable()                {                    @Override                    public void run()                    {                        userLoginView.showFailedError();                        userLoginView.hideLoading();                    }                });            }        });    }    public void clear()    {        userLoginView.clearUserName();        userLoginView.clearPassword();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

注意上述程式碼,我們的presenter完成二者的互動,那麼肯定需要二者的實現類。大致就是從View中獲取需要的引數,交給Model去執行業務方法,執行的過程中需要的反饋,以及結果,再讓View進行做對應的顯示。

ok,拿到一個例子經過上述的分解基本就能完成。以上純屬個人見解,歡迎討論和吐槽~ have a nice day ~。

     

微信公眾號:hongyangAndroid   (歡迎關注,第一時間推送博文資訊)  

參考資料