前言##
在日常開發(fā)APP 的過程中卷哩,隨著業(yè)務(wù)的擴展,規(guī)模的變化属拾。我們的代碼規(guī)模也會逐漸變得龐大将谊,每一個類里的代碼也會逐漸增多冷溶。尤其是Activity和Fragment ,由于Context 的存在瓢娜,基本上所有對視圖的操作我們只能在Activity和Fragment中完成挂洛;即便是對某些邏輯進行封裝,Activity和Fragment 依舊會顯得過于臃腫眠砾。因此虏劲,我們需要換一種思路去寫代碼,這個時候MVP模式就應(yīng)用而生了褒颈!那么MVP 怎么用呢柒巫,下面就來說一說。
假設(shè)你現(xiàn)在如要實現(xiàn)下圖中的功能:
這個需求很簡單谷丸,就是點擊按鈕堡掏,下載一張圖片,顯示下載進度刨疼;下載完成后泉唁,在ImageView中顯示這張圖片。
下面我們就分別用傳統(tǒng)的方式(也就是所謂的MVC)和MVP 模式分別取實現(xiàn)這個功能揩慕。然后分析一下MVP 到底好在哪里亭畜。
MVC##
public class MVCActivity extends AppCompatActivity {
private Context mContext;
private ImageView mImageView;
private MyHandler mMyHandler;
private ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
mContext = this;
init();
}
private void init() {
//view init
mImageView = (ImageView) findViewById(R.id.image);
mMyHandler = new MyHandler();
progressDialog = new ProgressDialog(mContext);
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
progressDialog.dismiss();
}
});
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setTitle("下載文件");
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
//click-event
findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
progressDialog.show();
HttpUtil.HttpGet(Constants.DOWNLOAD_URL, new DownloadCallback(mMyHandler));
}
});
findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
progressDialog.show();
HttpUtil.HttpGet(Constants.DOWNLOAD_ERROR_URL, new DownloadCallback(mMyHandler));
}
});
}
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 300:
int percent = msg.arg1;
if (percent < 100) {
progressDialog.setProgress(percent);
} else {
progressDialog.dismiss();
Glide.with(mContext).load(Constants.LOCAL_FILE_PATH).into(mImageView);
}
break;
case 404:
progressDialog.dismiss();
Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
}
}
用mvc的方式,一個Activity就能搞定迎卤。代碼邏輯很簡單拴鸵,點擊按鈕后顯示之前初始化好ProgressDialog析既,然后開始下載任務(wù)(這里HttpUtil 內(nèi)部簡單封裝了OKHttp 的異步GET請求正驻,實現(xiàn)下載文件保存到本地的功能,實現(xiàn)細節(jié)在此不做深入探討陨闹,有興趣的同學(xué)可以查看源碼)樟凄,然后將請求結(jié)果通過Handler返回聘芜,在handleMessage中根據(jù)返回數(shù)據(jù)的信息做出不同的UI 處理;下載成功時在ImageView中顯示圖片缝龄,下載失敗時Toast提示汰现。
可以發(fā)現(xiàn),在這種情況之前二拐,Activity的任務(wù)十分繁重服鹅,既要負責下載任務(wù)的具體實施,還要根據(jù)下載進行再次的邏輯判斷百新,才能去更新UI企软。這里只是一個簡單的任務(wù),你可能覺得無所謂饭望,但是實際開發(fā)中仗哨,一個Activity中有許多的交互事件形庭,這個時候Activity的代碼就顯得特別的龐大;一旦需求變更或出現(xiàn)bug厌漂,那簡直就是噩夢一場萨醒。
因此,我們希望Activity可以變成下面這樣
- 他負責發(fā)起處理和用戶交互的內(nèi)容苇倡,但又不負責具體的實現(xiàn)富纸;
- 需要顯示什么,不顯示什么旨椒,什么東西顯示多少晓褪,有個東西可以直接告訴他,
- Activity不再做復(fù)雜的邏輯處理综慎;
具體到上面的demo里就是涣仿,Activity負責發(fā)起下載任務(wù),但是不負責具體實現(xiàn)示惊;什么時候顯示ProgressDialog好港,顯示多少?什么時候提示錯誤信息米罚,這一切都希望有個東西能直接告訴Activity钧汹,而不再是在Activity里再做判斷。怎樣才能做到呢阔拳?那就得靠MVP 了崭孤。
MVP
MVP 模式所做的事情很簡單类嗤,就是將業(yè)務(wù)邏輯和視圖邏輯抽象到接口中糊肠。
怎么理解呢,我們就根據(jù)此次要實現(xiàn)的下載功能遗锣,用代碼說話货裹。
定義Model,View精偿,Presenter 接口###
Model Interface####
Model 接口定義所有需要實現(xiàn)的業(yè)務(wù)邏輯弧圆,在我們的下載任務(wù)中,業(yè)務(wù)邏輯只有一個笔咽,就是下載搔预;因此Model 接口可以這么定義 :
public interface IDownloadModel {
/**
* 下載操作
* @param url
*/
void download(String url);
}
View Interface####
View 接口定義所有需要實現(xiàn)的視圖邏輯,在我們的下載任務(wù)中叶组,視圖邏輯包括
- 顯示ProgressDialog拯田;
- 顯示Dialog具體進度;
- 顯示具體的View(設(shè)置圖片)甩十;
- 顯示錯誤信息(Toast提示)
因此View接口可以這么定義:
public interface IDownloadView {
/**
* 顯示進度條
* @param show
*/
void showProgressBar(boolean show);
/**
* 設(shè)置進度條進度
* @param progress
*/
void setProcessProgress(int progress);
/**
* 根據(jù)數(shù)據(jù)設(shè)置view
* @param result
*/
void setView(String result);
/**
* 設(shè)置請求失敗時的view
*/
void showFailToast();
}
Presenter Interface####
Presenter 接口作為連接Model和View的中間橋梁船庇,需要將二者連接起來吭产,因此他需要完成以下工作:
- 執(zhí)行下載任務(wù)
- 下載成功返回下載結(jié)果
- 下載過程返回下載進度
- 下載失敗回調(diào)
因此,Presenter 就可以這么定義:
public interface IDowndownPresenter {
/**
* 下載
* @param url
*/
void download(String url);
/**
* 下載成功
* @param result
*/
void downloadSuccess(String result);
/**
* 當前下載進度
* @param progress
*/
void downloadProgress(int progress);
/**
* 下載失敗
*/
void downloadFail();
}
接口Model鸭轮,View臣淤,Presenter 具體實現(xiàn)###
上面實現(xiàn)了,各個接口的定義窃爷,下面來看看他們具體的實現(xiàn):
Model 具體實現(xiàn)####
public class DownloadModel implements IDownloadModel {
private IDowndownPresenter mIDowndownPresenter;
private MyHandler mMyHandler = new MyHandler();
public DownloadModel(IDowndownPresenter IDowndownPresenter) {
mIDowndownPresenter = IDowndownPresenter;
}
@Override
public void download(String url) {
HttpUtil.HttpGet(url, new DownloadCallback(mMyHandler));
}
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 300:
int percent = msg.arg1;
if (percent < 100) {
mIDowndownPresenter.downloadProgress(percent);
} else {
mIDowndownPresenter.downloadSuccess(Constants.LOCAL_FILE_PATH);
}
break;
case 404:
mIDowndownPresenter.downloadFail();
break;
default:
break;
}
}
}
}
在MVP模式中邑蒋,Model的工作就是完成具體的業(yè)務(wù)操作,網(wǎng)絡(luò)請求按厘,持久化數(shù)據(jù)增刪改查等任務(wù)寺董。同時Model中又不會包含任何View。
這里Model的具體實現(xiàn)很簡單刻剥,將Http任務(wù)的結(jié)果返回到Handler當中遮咖,而在Handler中的實現(xiàn)又是由Presenter完成。那么Presenter接口又是怎樣實現(xiàn)的呢造虏?趕緊來看看
Presenter 具體實現(xiàn)####
public class DownloadPresenter implements IDowndownPresenter {
private IDownloadView mIDownloadView;
private IDownloadModel mIDownloadModel;
public DownloadPresenter(IDownloadView IDownloadView) {
mIDownloadView = IDownloadView;
mIDownloadModel = new DownloadModel(this);
}
@Override
public void download(String url) {
mIDownloadView.showProgressBar(true);
mIDownloadModel.download(url);
}
@Override
public void downloadSuccess(String result) {
mIDownloadView.showProgressBar(false);
mIDownloadView.setView(result);
}
@Override
public void downloadProgress(int progress) {
mIDownloadView.setProcessProgress(progress);
}
@Override
public void downloadFail() {
mIDownloadView.showProgressBar(false);
mIDownloadView.showFailToast();
}
}
可以看到御吞,我們在DownloadPresenter的構(gòu)造方法中,同時實例化了Model和View漓藕,這樣Presenter中就同時包含了兩者陶珠;這樣;在Presenter具體實現(xiàn)中享钞,業(yè)務(wù)相關(guān)的操作由Model去完成(例如download)揍诽,視圖相關(guān)的操作由View去完成(如setView等)。Presenter 作為橋梁的作用就這樣體現(xiàn)出來了栗竖,巧妙的將View和Model的具體實現(xiàn)連接了起來暑脆。
View具體實現(xiàn)####
最后再看一下View接口的具體實現(xiàn),也就是Activity的實現(xiàn):
public class MVPActivity extends AppCompatActivity implements IDownloadView {
private Context mContext;
private ImageView mImageView;
private ProgressDialog progressDialog;
private DownloadPresenter mDownloadPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
setContentView(R.layout.activity_mvp);
init();
}
private void init() {
mDownloadPresenter = new DownloadPresenter(this);
//view init
mImageView = (ImageView) findViewById(R.id.image);
findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDownloadPresenter.download(Constants.DOWNLOAD_URL);
}
});
findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDownloadPresenter.download(Constants.DOWNLOAD_ERROR_URL);
}
});
progressDialog = new ProgressDialog(mContext);
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
progressDialog.dismiss();
}
});
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setTitle("下載文件");
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
}
@Override
public void showProgressBar(boolean show) {
if (show) {
progressDialog.show();
} else {
progressDialog.dismiss();
}
}
@Override
public void setProcessProgress(int progress) {
progressDialog.setProgress(progress);
}
@Override
public void setView(String result) {
Glide.with(mContext).load(result).into(mImageView);
}
@Override
public void showFailToast() {
Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show();
}
}
在點下按鈕執(zhí)行開始下載任務(wù)的時候狐肢,View(Activity)中沒有具體的實現(xiàn)添吗,只是調(diào)用了Presenter中的download方法,而Presenter中的download又會去調(diào)用Model的download方法份名,Model又會在根據(jù)具體邏輯(在這里就是Http請求)的狀態(tài)去調(diào)用Presenter中的方法碟联,例如我們在handleMessage方法中,調(diào)用mIDowndownPresenter.downloadProgress(percent)時僵腺,就會去調(diào)用Presenter的具體實現(xiàn)
@Override
public void downloadProgress(int progress) {
mIDownloadView.setProcessProgress(progress);
}
而他的內(nèi)部實現(xiàn)又是操作具體的View鲤孵,也就是我們在Activity中初始化Presenter中傳遞的this,也就是當前Activity(View)辰如,這樣最終回到了Activity中的
@Override
public void setProcessProgress(int progress) {
progressDialog.setProgress(progress);
}
我們?yōu)閜rogressDialog 設(shè)置進度普监。
至此,我們就通過MVP 的模式實現(xiàn)了我們之前所設(shè)想的Activity
- Button的click方法負責發(fā)起下載任務(wù),但又不負責具體實現(xiàn)鹰椒,而是由Presenter轉(zhuǎn)接給Model去實現(xiàn)
- Activity 什么時候顯示ProgressDialog锡移,什么時候顯示Toast直接由Presenter告訴他,他只做一個View想做的事情
- Activity里沒有任何邏輯處理漆际,所有的邏輯判斷都在Model中完成了淆珊。
這就是MVP !<榛恪施符!
MVC VS MVP##
通過上面的兩種實現(xiàn)方案,相信每個人都已經(jīng)理解了MVC和MVP的區(qū)別擂找;下面就其各自的優(yōu)缺點再做一下總結(jié)戳吝;當然,這里的優(yōu)缺點只是相對而言贯涎。
優(yōu)點####
上面兩張圖分別是MVC和MVP架構(gòu)圖听哭。相信許多和我一樣嘗試去學(xué)習和了解MVP架構(gòu)的同學(xué)對這兩圖(或類似的圖)并不陌生。
結(jié)構(gòu)更加清晰
我們回過頭再去看MVCActivity 的實現(xiàn)塘雳,暫且將我們對Http請求的封裝歸結(jié)為Model (M)陆盘,那么剩下的就只有Activity了,而這個Activity即負責實現(xiàn)視圖邏輯败明,又需要實現(xiàn)部分業(yè)務(wù)邏輯隘马,也就是說他既是Controller(C) 又是 View(V)。V和C的劃分完全不清晰妻顶;因此酸员,傳統(tǒng)的代碼結(jié)構(gòu)只能勉強稱為MV 或者是MC,如果算上xml 的布局文件讳嘱,才能牽強的稱為MVC 結(jié)構(gòu)幔嗦。
而MVP 就不同了,Model呢燥,View崭添,Presenter各司其職寓娩,互相搭配叛氨,實現(xiàn)了解耦,完全解放了Activity(或者是Fragment)棘伴。這就是MVP 的優(yōu)勢寞埠,代碼結(jié)構(gòu)更加清晰『缚洌可以這樣說仁连,同一個模塊的實現(xiàn),甚至允許幾個人分工完成;假設(shè)有一個非常復(fù)雜的Activity饭冬,如果使用MVP 的模式開發(fā)使鹅;那么這個時候,定義好MVP的接口之后昌抠,就可以有人專門去做Model患朱,另一個人專門去做View;再由一個人寫Presenter的代碼炊苫,當然這需要極強的代碼規(guī)范和協(xié)作能力裁厅;但這在傳統(tǒng)的MVC模式中根本是無法想象的,所有的東西都在一個類里侨艾,兩個人一起改执虹,有了沖突怎么玩/(ㄒoㄒ)/~~。
需求變更唠梨,不再是噩夢
假設(shè)現(xiàn)在有新的需求袋励,產(chǎn)品經(jīng)理認為下載失敗后只有一個Toast提示太單調(diào)了(而且用戶有可能錯過了這Toast的顯示,而誤以為APP失去了響應(yīng))当叭,因此插龄,現(xiàn)在希望在下載失敗后彈出一個Dialog,可以重試下載任務(wù)科展。是想均牢,如果代碼使用傳統(tǒng)的MVC 結(jié)構(gòu),恰巧這個代碼不是你寫的才睹,或者說就是你寫的徘跪,但是你已經(jīng)忘記了具體的邏輯;那么為了實現(xiàn)這個需求你又得去重新捋一遍邏輯琅攘,到某個類的xxx行進行修改垮庐;但是如果使用MVP就不同了View接口已經(jīng)定義好了showFailToast就是用來顯示錯誤提示的;因此即便代碼不是你寫的坞琴,你都可以很快的找到哨查,應(yīng)該去哪里改;而省去很多時間剧辐。
更容易寫單元測試
這個就不展開說了寒亥,總之寫過單元測試的人應(yīng)該都有這樣的體會。
缺點####
MVP這么好荧关,也不是沒有缺點溉奕。
如圖中所示,使用MVP 架構(gòu)之后忍啤,多出了許多類加勤;這是必然的;每一個View(Activity或Fragment)都至少需要各自的Model、Presenter和View接口鳄梅,在加上他們各自的實現(xiàn)叠国,也就是說每一個頁面都會有6個java文件(算上Fragment或Activity,因為他就是View的實現(xiàn))戴尸,這樣一個稍有點規(guī)模的APP煎饼,類就會變得異常的多,而每一個類的加載又會消耗資源校赤;因此吆玖,相較于MVC,這算是MVP最大的缺點了吧马篮。
當然沾乘,對于這個問題我們可以通過泛型參數(shù)、抽象父類的方式浑测,將一些公用的Model及Presenter抽象出來翅阵。這應(yīng)該就是使用MVP架構(gòu)的精髓了。
最后##
個人感覺迁央,使用MVP 架構(gòu)是利大于弊的掷匠;隨著項目規(guī)模的增加,代碼邏輯的清晰才是最重要的事情岖圈。況且Google官方也出推出了一系列關(guān)于MVP的使用demo讹语。因此,這也是官方提倡大家使用的蜂科。凡事顽决,有利必有弊;類數(shù)目的增長是無法避免的事情导匣,因此如何使用泛型和抽象優(yōu)化MVP 的結(jié)構(gòu)就變成了我們用好MVP的關(guān)鍵了才菠。
當然,我們不能為了MVP而去MVP贡定,如果項目結(jié)構(gòu)不是很龐大赋访,業(yè)務(wù)不是很復(fù)雜;那么傳統(tǒng)的MVC 架構(gòu)足以缓待,而且也方便蚓耽!
年前的最后一個工作日了,我居然寫了一篇學(xué)習筆記命斧;今天一定是上了假的班兒田晚!明天回家過年,O(∩_∩)O哈哈哈~国葬!每一個人,新年快樂!