淺談 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,先簡單瞭解下,文中會有例子到時候可以直觀的感受下。
小總結下,也就是說,之所以讓人覺得耳目一新,是因為這次的跳躍是從並不標準的MVC
到MVP
的一個轉變,減少了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 (歡迎關注,第一時間推送博文資訊)