本項(xiàng)目github地址:https://github.com/samonkey-zouyingjun/mvp
轉(zhuǎn)載請注明出處:http://www.reibang.com/p/9cee97587006
反思源于工作巡扇,卻高于工作
MVC和MVP的區(qū)別大家都懂痛悯,簡單的用一副圖片就可以概括乙埃,這種層次的背書應(yīng)付面試還行垄惧,但是不求甚解恐怕永遠(yuǎn)領(lǐng)會不到精髓升略,怎沒有金剛鉆怎攬瓷器活反肋?接下來我通過一些工作中所遇到的問題來談?wù)刴vp設(shè)計(jì)模式的前世今生尼摹,在文章末尾加上對mvp的案例以及分析和措,希望對大家有所幫助,本文案例比較適合mvp初學(xué)者助被,附錄給出了一些進(jìn)階學(xué)習(xí)的建議剖张。
問題一
隨著界面和業(yè)務(wù)邏輯復(fù)雜度不斷提升,Activity的代碼就會越來越用臃腫揩环,一個復(fù)雜一點(diǎn)的Activity常常是幾千行代碼搔弄,維護(hù)起來特別亂,這又是為何丰滑?
1.首先我們來分析一下傳統(tǒng)的mvc模式:
- Modle層:適合做一些業(yè)務(wù)邏輯處理顾犹,比如數(shù)據(jù)庫存取操作,網(wǎng)絡(luò)操作吨枉,復(fù)雜的算法等耗時的任務(wù)蹦渣。
- View層:應(yīng)用層中處理數(shù)據(jù)顯示的部分,XML布局可以視為V層貌亭,顯示Model層的數(shù)據(jù)結(jié)果。
- Controller層:在Android中认臊,Activity處理用戶交互問題圃庭,因此可以認(rèn)為Activity是控制器,Activity讀取V視圖層的數(shù)據(jù)(eg.讀取當(dāng)前EditText控件的數(shù)據(jù))失晴,控制用戶輸入(eg.EditText控件數(shù)據(jù)的輸入)剧腻,并向Model發(fā)送數(shù)據(jù)請求(eg.點(diǎn)擊Button發(fā)起網(wǎng)絡(luò)請求等)。
從上面可以知道MVC在安卓中涂屁,Activity并不是一個標(biāo)準(zhǔn)的Controller(處理用戶的交互請求和響應(yīng))书在,也需要做View層的工作(加載布局和初始化用戶界面),這就導(dǎo)致了V層和Controler層的偶和度較高拆又。
2.再來看看mvp是如何改進(jìn)這一問題的:
- Modle層:和原來一樣儒旬,適合做一些耗時的業(yè)務(wù)邏輯處理。
- View層:明確定義為Activity帖族,負(fù)責(zé)UI元素的初始化栈源,建立UI元素與Presenter的關(guān)聯(lián)(Listener之類),同時自己也會處理一些簡單的邏輯(復(fù)雜的邏輯交由 Presenter處理).
- Presenter層:負(fù)責(zé)復(fù)雜的邏輯處理竖般,對應(yīng)各種實(shí)現(xiàn)類和回調(diào)方法
從MVP模式中我們也可以看到一些明顯的改變甚垦,弱化了Activity的職責(zé),讓其變得和輕薄,只負(fù)責(zé)顯示數(shù)據(jù)艰亮、提供友好界面和交互就行闭翩;其次是在原來Activity和Modle層中又剝離出了各種接口的實(shí)現(xiàn)類,通過回調(diào)來傳遞數(shù)據(jù)迄埃。
3.所以MVP相比MVC的好處:業(yè)務(wù)結(jié)構(gòu)清晰疗韵,而且將來更換實(shí)現(xiàn)類不用修改業(yè)務(wù)結(jié)構(gòu),原因就是presenter就是實(shí)現(xiàn)類调俘,把model和View完全解耦伶棒。壞處就是:分層多了,邏輯會更繞彩库。
問題二
Android應(yīng)用做單元測試肤无,一般都是部署到虛擬機(jī)或者真機(jī)上再模擬操作進(jìn)行測試,而這將耗費(fèi)大量不必要的時間骇钦,如何節(jié)省了不必要的部署和測試時間宛渐?
從問題一的分析我們知道,傳統(tǒng)的mvc模式下Controller層和View層耦合都較高難以分離眯搭,所以一般都是通過部署來測試窥翩。但是再M(fèi)VP模式中,Presenter和Activity中是通過接口來進(jìn)行交互鳞仙,我們只需要去自定義類實(shí)現(xiàn)來這個接口寇蚊,再這個類中來模擬Activity調(diào)用就可以進(jìn)行單元測試,開發(fā)效率大大提高棍好。
從mvp到設(shè)計(jì)模式
1.一句話簡單概括mvp
mvp是安卓中面向接口編程的典型仗岸,presenter通過view和modle接口的引用,來調(diào)用具體實(shí)現(xiàn)類的方法
2.三層架構(gòu)
對于各種架構(gòu)思想借笙,三層架構(gòu)和MVP等模式有異曲同工之妙扒怖。三層架構(gòu)是從整個程序架構(gòu)的角度來分為WEB(界面層)、DAL(數(shù)據(jù)訪問層)和BLL(業(yè)務(wù)邏輯層)各司其職业稼,分工明確盗痒。對于程序員來說也是為了在不同階段更加注重某階段業(yè)務(wù)邏輯處理。
2.萬變不離其宗低散,不管哪種設(shè)計(jì)模式俯邓,其優(yōu)化目的都是:
- 易于維護(hù)
- 易于測試
- 松耦合度
- 復(fù)用性高
- 健壯穩(wěn)定
案例與分析
此項(xiàng)目分包是根據(jù)功能模塊來分的(登陸和主頁數(shù)據(jù)顯示兩個模塊)。
login中的mvp分塊
Modle層:對應(yīng)于longinInteractorImple實(shí)現(xiàn)了longinInteractor
View層:對應(yīng)于LoginActivity實(shí)現(xiàn)了LoginView接口
Presenter層:對應(yīng)于LoginPreseenterImpl實(shí)現(xiàn)了LoginPresenter接口
login模塊中的mvp實(shí)現(xiàn)
1.先來看看View層都干了那些事情
public interface LoginView {
void showProgress();
void hideProgress();
void setUsernameError();
void setPasswordError();
void navigateToHome();
}
public class LoginActivity extends AppCompatActivity implements View.OnClickListener, LoginView {
private ProgressBar progressBar;
private EditText username;
private EditText password;
private LoginPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login); //初始化UI
progressBar = findViewById(R.id.progress);
username = findViewById(R.id.username);
password = findViewById(R.id.password);
findViewById(R.id.button).setOnClickListener(this); //綁定監(jiān)聽
presenter = new LoginPresenterImpl(this); //建立UI元素與Presenter的關(guān)聯(lián)
}
@Override
protected void onDestroy() {
presenter.onDestroy();
super.onDestroy();
}
@Override
public void onClick(View view) {
presenter.validateCredentials(username.getText().toString(),password.getText().toString());
}
@Override
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void setUsernameError() {
username.setError(getString(R.string.username_error));
}
@Override
public void setPasswordError() {
password.setError(getString(R.string.password_error));
}
@Override
public void navigateToHome() {
startActivity(new Intent(this, MainActivity.class));
}
}
可以看到現(xiàn)在Activity主要負(fù)責(zé)的就是以下三件事:
- 初始化UI
- 綁定監(jiān)聽
- 建立UI元素與Presenter的關(guān)聯(lián)
2.再來看看Presenter層干了那些事情
public interface LoginPresenter {
void validateCredentials(String username,String password);
void onDestroy();
}
public class LoginPresenterImpl implements LoginPresenter, LoginInteractor.OnloginFinishedListener {
private LoginView loginView;
private LoginInteractor interactor;
public LoginPresenterImpl(LoginView loginView) {
this.loginView = loginView;
this.interactor = new LoginInteractorImpl();
}
@Override
public void validateCredentials(String username, String password) {
if(loginView != null){
loginView.showProgress();
}
interactor.login(username,password,this); //關(guān)聯(lián)Modle層
}
@Override
public void onDestroy() {
loginView = null;
}
@Override
public void onUsernameError() {
if(loginView != null){
loginView.setUsernameError();
loginView.hideProgress();
}
}
@Override
public void onPasswordError() {
if(loginView != null){
loginView.setPasswordError();
loginView.hideProgress();
}
}
@Override
public void onSuccess() {
if(loginView != null){
loginView.navigateToHome();
}
}
}
可以看到P層主要是處理loginActivity傳給LoginPresenterImpl 邏輯業(yè)務(wù)谦纱,并在需要訪問數(shù)據(jù)的時候關(guān)聯(lián)了M層看成。
3.最后來看看Modle層干了那些事情
public interface LoginInteractor {
interface OnloginFinishedListener{
void onUsernameError();
void onPasswordError();
void onSuccess();
}
void login(String username,String password,OnloginFinishedListener listener);
}
public class LoginInteractorImpl implements LoginInteractor {
@Override
public void login(final String username, final String password, final OnloginFinishedListener listener) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
boolean error = false;
if(TextUtils.isEmpty(username)){
listener.onUsernameError(); //處理P層傳入的邏輯
error = true;
return;
}
if(TextUtils.isEmpty(password)){
listener.onPasswordError();
error = true;
return;
}
if (!error){
listener.onSuccess();
}
}
},2000);
}
}
這登陸模塊的modle層未涉及到數(shù)據(jù)訪問,只是做了模擬操作跨嘉。
4.總結(jié)
總體來說mvp給人的感覺是很爽快的川慌,特別是activity中的書寫更是簡明清爽吃嘿,在activity中只是看到Ui監(jiān)聽的代碼和P層綁定代碼,而具體邏輯則在P層中實(shí)現(xiàn)梦重,P層中涉及到數(shù)據(jù)訪問則綁定M層兑燥,然后在M層中處理相應(yīng)數(shù)據(jù)的封裝,再把結(jié)果給P層琴拧,P處理業(yè)務(wù)后在給V顯示〗低現(xiàn)在再看文章頭的關(guān)系圖是不是更親切了呢?(●'?'●)
總得來說mvp用的不是很多蚓胸,也沒有說非要遵從這個模式挣饥,模式始終都是為程序員服務(wù)的,每種模式都是各有弊利沛膳。對于初學(xué)者來說不建議對大項(xiàng)目用mvp扔枫,可以先從小項(xiàng)目上嘗試使用,熟能生巧锹安。以下附錄提供進(jìn)階建議短荐。
附錄:
Introduction-to-Model-View-Presenter-on-Android 英文翻譯版(MVP經(jīng)典必讀)
Introduction-to-Model-View-Presenter-on-Android 中文翻譯版
ZhiHuMVP(MVP配合RxJava 響應(yīng)式編程)
ActivityFragmentMVP github地址(MVP處理Activity和Fragment,Dagger 注入)
Material-Movies github地址( 使用material design +MVP實(shí)現(xiàn)的Material-Movies)
androidmvp(star2000+的MVP實(shí)例)
MVP for Android: how to organize the presentation layer(star2000+MVP的講解)