導(dǎo)語:最近公眾號后臺經(jīng)常收到一些消息炮姨,說能不能講一些開發(fā)模式捌刮,經(jīng)過思考后,我決定講一講MVP模式舒岸。希望對大家能夠有所幫助绅作。并寫了一個簡單的小demo。
背景
看到MVP蛾派,大家肯定會想什么是MVP呢俄认?這個我可以肯定的告訴大家MVP(Most Valuable Player)是最有價值球員的意思,這當(dāng)然是開玩笑了洪乍。之所以會出現(xiàn)MVP這種架構(gòu)模式眯杏,是因為我相信大家在開發(fā)App時,肯定會發(fā)現(xiàn)壳澳,Activity的負擔(dān)非常重岂贩,既要初始化控件,又要寫一些邏輯操作的展示等等巷波,有時候很多Activity中的代碼都充當(dāng)了Controller和Model的角色萎津,所以你會發(fā)現(xiàn)Activity違背單一職責(zé)原則,負擔(dān)過重褥紫。所以姜性,就出現(xiàn)了這么一種架構(gòu)模式,叫MVP髓考,并不是最有價值球員哦部念。
什么是MVP架構(gòu)
MVP就是Model-View-Presenter,MVP是從經(jīng)典的模式MVC演變而來氨菇,它們的基本思想有相通的地方:Controller/Presenter負責(zé)邏輯的處理儡炼,Model提供數(shù)據(jù),View負責(zé)顯示查蓉。作為一種新的模式乌询,MVP與MVC有著一個重大的區(qū)別:在MVP中View并不直接使用Model,它們之間的通信是通過Presenter (MVC中的Controller)來進行的豌研,所有的交互都發(fā)生在Presenter內(nèi)部妹田,而在MVC中View會直接從Model中讀取數(shù)據(jù)而不是通過 Controller。在MVC里鹃共,View是可以直接訪問Model的鬼佣!從而,View里會包含Model信息霜浴,不可避免的還要包括一些業(yè)務(wù)邏輯晶衷。 在MVC模型里,更關(guān)注的Model的不變,而同時有多個對Model的不同顯示晌纫,及View税迷。所以,在MVC模型里锹漱,Model不依賴于View箭养,但是View是依賴于Model的。不僅如此凌蔬,因為有一些業(yè)務(wù)邏輯在View里實現(xiàn)了露懒,導(dǎo)致要更改View也是比較困難的闯冷,至少那些業(yè)務(wù)邏輯是無法重用的砂心。用流程圖的方式解釋就更清楚了:
MVP和MVC的區(qū)別,及MVP是如何解決MVC的問題蛇耀?
MVP架構(gòu):
View: 對應(yīng)于Activity辩诞,負責(zé)View的繪制以及與用戶交互
Model: 依然是業(yè)務(wù)邏輯和實體模型
Presenter: 負責(zé)完成View于Model間的交互
View不直接與Model交互,而是通過與Presenter交互來與Model間接交互纺涤。
Presenter與View的交互是通過接口來進行的译暂。
通常View與Presenter是一對一的,但復(fù)雜的View可能綁定多個Presenter來處理邏輯撩炊。
MVC架構(gòu):
View:對應(yīng)于布局文件
Model:業(yè)務(wù)邏輯和實體模型
Controllor:對應(yīng)于Activity
View可以與Model直接交互外永。
Controller是基于行為的,并且可以被多個View共享拧咳。
可以負責(zé)決定顯示哪個View伯顶。
總結(jié)解釋一下就是說:
從MVC到MVP的一個轉(zhuǎn)變,就是減少了Activity的職責(zé)骆膝,減輕了它的負擔(dān)祭衩,簡化了Activity中的代碼和一些操作,將邏輯代碼提取到了Presenter中進行處理阅签,降低了其耦合度掐暮。
進一步的解釋:
在MVP里,Presenter完全把Model和View進行了分離政钟,主要的程序邏輯在Presenter里實現(xiàn)路克。而且,Presenter與具體的View是沒有直接關(guān)聯(lián)的养交,而是通過定義好的接口進行交互精算,從而使得在變更View時候可以保持Presenter的不變,即重用层坠! 不僅如此殖妇,我們還可以編寫測試用的View,模擬用戶的各種操作破花,從而實現(xiàn)對Presenter的測試--而不需要使用自動化的測試工具谦趣。 我們甚至可以在Model和View都沒有完成時候疲吸,就可以通過編寫Mock Object(即實現(xiàn)了Model和View的接口,但沒有具體的內(nèi)容的)來測試Presenter的邏輯前鹅。 在MVP里摘悴,應(yīng)用程序的邏輯主要在Presenter來實現(xiàn),其中的View是很薄的一層舰绘。因此就有人提出了Presenter First的設(shè)計模式蹂喻,就是根據(jù)User Story來首先設(shè)計和開發(fā)Presenter。在這個過程中捂寿,View是很簡單的口四,能夠把信息顯示清楚就可以了。在后面秦陋,根據(jù)需要再隨便更改View蔓彩,而對Presenter沒有任何的影響了滤蝠。 如果要實現(xiàn)的UI比較復(fù)雜巾遭,而且相關(guān)的顯示邏輯還跟Model有關(guān)系,就可以在View和Presenter之間放置一個Adapter辅髓。由這個 Adapter來訪問Model和View顺又,避免兩者之間的關(guān)聯(lián)更卒。而同時,因為Adapter實現(xiàn)了View的接口稚照,從而可以保證與Presenter之間接口的不變蹂空。這樣就可以保證View和Presenter之間接口的簡潔,又不失去UI的靈活性锐锣。 在MVP模式里腌闯,View只應(yīng)該有簡單的Set/Get的方法,用戶輸入和設(shè)置界面顯示的內(nèi)容雕憔,除此就不應(yīng)該有更多的內(nèi)容姿骏,絕不容許直接訪問Model--這就是與MVC很大的不同之處。
MVP的優(yōu)點
1.降低耦合度斤彼,隱藏數(shù)據(jù)分瘦,Activity中代碼更簡潔
2.模塊職責(zé)劃分明顯3.方便測試驅(qū)動開發(fā)4.代碼復(fù)用度較高5.代碼靈活性
MVP架構(gòu)模式實例
這個實例是根據(jù)用戶id獲取用戶信息并展示的一個過程,其中獲取用戶信息用了一個線程進行了模擬獲取琉苇。希望大家能夠看懂嘲玫,并對大家有所幫助。**
我們先看一下MVP目錄結(jié)構(gòu)圖
1并扇、Model層
首先是一個javabean User實體類
package net.loonggg.mvpdemo.bean;
public class User {
private String name;
private String id;
private String sex;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
Model層抽象接口實現(xiàn):
package net.loonggg.mvpdemo.model;
import net.loonggg.mvpdemo.bean.User;
public class GetUserInfo implements IGetUser {
@Override
public void getUserInfo(final int id, final OnUserInfoListener listener) {
// 模擬子線程耗時操作
new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模擬信息獲取成功
if (id == 1) {
User user = new User();
user.setName("非著名程序員");
user.setAge("26");
user.setSex("男");
user.setId("1");
listener.getUserInfoSuccess(user);
} else {
listener.getUserInfoFailed();
}
}
}.start();
}
}
Model層抽象接口
package net.loonggg.mvpdemo.model;
public interface IGetUser {
public void getUserInfo(int id, OnUserInfoListener listener);
}
2去团、View層
我們都知道Presenter與View交互是通過接口,所以我們需要定義一個IShowUserView的接口,這個接口封裝的方法基本上都跟視圖展示有關(guān)土陪。
package net.loonggg.mvpdemo.view;
import net.loonggg.mvpdemo.bean.User;
public interface IShowUserView {
void showLoading();
void hideLoading();
void toMainActivity(User user);
void showFailedError();
}
3昼汗、Presenter層
Presenter是Model和View之間交互的橋梁,里面有一些業(yè)務(wù)邏輯的操作鬼雀。
package net.loonggg.mvpdemo.presenter;
import net.loonggg.mvpdemo.bean.User;
import net.loonggg.mvpdemo.model.GetUserInfo;
import net.loonggg.mvpdemo.model.IGetUser;
import net.loonggg.mvpdemo.model.OnUserInfoListener;
import net.loonggg.mvpdemo.view.IShowUserView;
import android.os.Handler;
public class UserInfoPresenter {
private IGetUser iGetUser;
private IShowUserView iShowUserView;
private Handler mHandler = new Handler();
public UserInfoPresenter(IShowUserView iShowUserView) {
this.iShowUserView = iShowUserView;
this.iGetUser = new GetUserInfo();
}
public void getUserInfoToShow(int id) {
iShowUserView.showLoading();
iGetUser.getUserInfo(id, new OnUserInfoListener() {
@Override
public void getUserInfoSuccess(final User user) {
// 需要在UI線程執(zhí)行
mHandler.post(new Runnable() {
@Override
public void run() {
iShowUserView.toMainActivity(user);
iShowUserView.hideLoading();
}
});
}
@Override
public void getUserInfoFailed() {
mHandler.post(new Runnable() {
@Override
public void run() {
iShowUserView.showFailedError();
iShowUserView.hideLoading();
}
});
}
});
}
}
4顷窒、Activity中的調(diào)用
package net.loonggg.mvpdemo;
import net.loonggg.mvpdemo.bean.User;
import net.loonggg.mvpdemo.presenter.UserInfoPresenter;
import net.loonggg.mvpdemo.view.IShowUserView;
import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements IShowUserView {
private Button btn;
private TextView name_tv, age_tv, sex_tv;
private ProgressDialog pd = null;
private UserInfoPresenter userInfoPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
userInfoPresenter = new UserInfoPresenter(this);
btn = (Button) findViewById(R.id.btn);
name_tv = (TextView) findViewById(R.id.name_tv);
age_tv = (TextView) findViewById(R.id.age_tv);
sex_tv = (TextView) findViewById(R.id.sex_tv);
pd = new ProgressDialog(this);
pd.setMessage("正在加載……");
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userInfoPresenter.getUserInfoToShow(1);
}
});
}
@Override
public void showLoading() {
pd.show();
}
@Override
public void hideLoading() {
pd.cancel();
}
@Override
public void toMainActivity(User user) {
name_tv.setText(user.getName());
age_tv.setText(user.getAge());
sex_tv.setText(user.getSex());
}
@Override
public void showFailedError() {
Toast.makeText(this, "獲取信息有誤", Toast.LENGTH_SHORT).show();
}
}
結(jié)語:看完實例代碼,有點感覺了吧源哩?俗話說好記性不如爛筆頭鞋吉,看不如寫,試著自己去寫一個励烦,領(lǐng)會一下其中的精神谓着,相信你會豁然開朗。當(dāng)然有人說這么做崩侠,是不是又多了一層漆魔,感覺又麻煩了,是嗎却音?降低了耦合度,提取了代碼矢炼,并增加了復(fù)用系瓢,代碼更簡潔,其實好處還是很多的句灌。