1. 對於 MVP 常常有許多人覺得,MVP 和 MVC 也沒有什麼區別啊。MVP 框架不過只是一個噱頭吧。說實在的,剛接觸 MVP 的那段時間,我也有同樣的困惑。大家天天說的 MVP,怎麼看上去和 MVC 那麼像呢。除了代碼變多了,怎麼一點也看不到 MVP 有什麼用,有什麼區別。隨着反覆的理解 MVP 模式,看了大量網上所謂“牛人”寫的各種 MVP(實際許多理解都不透徹),集百家之長,有一天,我讀了一篇真正阿里雲大牛的博客(鏈接不記得了)才真有醍醐灌頂的感覺。知道什麼是 MVP。
2. 本文核心,一定不要把 P 層寫成網絡層,一定不要把 P 層寫成 M 層,切記。P 層是寫業務邏輯的,V 層是寫視圖控制邏輯的,M 層是管理數據的。
3. 下面以一個簡單的 EvaluatingResultActivity 來說明其區別。當界面變得複雜時,更加需要重視這一塊的區別。
一、View 層的視圖控制代碼與 P 層業務邏輯代碼混合在一起。
界面的視圖控制代碼如下
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.article_activity_evaluating_result);
Toolbar toolbar = findViewById(R.id.toolbar);
initToolBar(toolbar);
mToolbarView.setLeftTitle("評測結果");
mToolbarView.setShareIcon(R.drawable.ic_share_black);
mHeaderView = findViewById(R.id.head_view);
tabLayout = findViewById(R.id.fea_aspirinTabLayout);
viewPager = findViewById(R.id.viewpager);
}
showLoadingView 的實現
...
showEmptyView 的實現
...
showContentView 的實現
...
界面初始化時的業務邏輯代碼如下
if (mIsDemo) {
getEvalResultDemo(mEvalId);
} else {
getEvalResult(mEvalId);
}
邏輯1:界面剛打開時,需要緩解用戶焦慮。(展示 loading)
mView.showLoadingView();
邏輯2:網絡異常,取不到數據時需要通知用戶。(展示 empty)
mView.showEmptyView();
邏輯3:數據正常時,需要展示到用戶面前。(展示 content)
mView.showContentView();
界面初始化時的跨界面數據初始化代碼如下
int evalId = getIntent().getIntExtra(ModuleArticle.EvaluatingResultActivity.EXTRA_INT_EVAL_ID, 0);
boolean isDemo = getIntent().getBooleanExtra(ModuleArticle.EvaluatingResultActivity.EXTRA_BOOLEAN_IS_DEMO, false);
int evalId = getIntent().getIntExtra(ModuleArticle.EvaluatingResultActivity.EXTRA_INT_EVAL_ID, 0);
boolean isDemo = getIntent().getBooleanExtra(ModuleArticle.EvaluatingResultActivity.EXTRA_BOOLEAN_IS_DEMO, false);
這裏爲啥需要寫兩遍,是因爲可能 P 層和 V 層都需要。
有兩個方式,一是寫兩遍,二是傳值或者 V 層另外提供 get 方法。
舊的邏輯,View 層的視圖控制代碼 、P 層業務邏輯代碼 、 跨界面數據初始化代碼混合在一起
private int evalId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.article_activity_evaluating_result);
Toolbar toolbar = findViewById(R.id.toolbar);
initToolBar(toolbar);
mToolbarView.setLeftTitle("評測結果");
mToolbarView.setShareIcon(R.drawable.ic_share_black);
mHeaderView = findViewById(R.id.head_view);
tabLayout = findViewById(R.id.fea_aspirinTabLayout);
viewPager = findViewById(R.id.viewpager);
evalId = getIntent().getIntExtra(ModuleArticle.EvaluatingResultActivity.EXTRA_INT_EVAL_ID, 0);
boolean isDemo = getIntent().getBooleanExtra(ModuleArticle.EvaluatingResultActivity.EXTRA_BOOLEAN_IS_DEMO, false);
showLoadingView();
if (isDemo) {
mPresenter.getEvalResultDemo();
} else {
mPresenter.getEvalResult(evalId);
}
}
新的邏輯,V層邏輯、P層邏輯及數據傳遞邏輯分離
新的 P 層
@Override
public void takeView(EvaluatingResultContract.View view) {
super.takeView(view);
mView.showLoadingView();
if (mIsDemo) {
getEvalResultDemo(mEvalId);
} else {
getEvalResult(mEvalId);
}
}
新的 V 層
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.article_activity_evaluating_result);
Toolbar toolbar = findViewById(R.id.toolbar);
initToolBar(toolbar);
mToolbarView.setLeftTitle("評測結果");
mToolbarView.setShareIcon(R.drawable.ic_share_black);
mHeaderView = findViewById(R.id.head_view);
tabLayout = findViewById(R.id.fea_aspirinTabLayout);
viewPager = findViewById(R.id.viewpager);
}
新的注入 Module
@ActivityScope
@Provides
@Named("isDemo")
static boolean isDemo(EvaluatingResultActivity evaluatingResultActivity){
boolean isDemo = evaluatingResultActivity.getIntent().getBooleanExtra(ModuleArticle.EvaluatingResultActivity.EXTRA_BOOLEAN_IS_DEMO, false);
return isDemo;
}
@ActivityScope
@Provides
@Named("evalId")
static int provideEvalId(EvaluatingResultActivity evaluatingResultActivity){
int evalId = evaluatingResultActivity.getIntent().getIntExtra(ModuleArticle.EvaluatingResultActivity.EXTRA_INT_EVAL_ID, 0);
return evalId;
}
區別
M 、V 、 P 邏輯代碼分離。
當需要捋 P 層業務邏輯時,從 P 層入口方法 takeView 入手,如果讀書一個,一氣呵成,無 V 層各種邏輯干擾。
擴展
界面初始化需要添加一個新的邏輯分支時,永遠只需要修改 P 層的入口方法,添加一個 else if ,而不用擔心邏輯嵌套問題。因爲 V 層沒有邏輯,業務邏輯都在 P 層的入口方法裏面。
二、永遠不會有人知道 onCreate 裏面的 showLoadingView 與 showEvalResult 方法裏面的 showContentView 是一個組合。(即 P V 邏輯交織的一種)
假設寫 View 的和寫 P 層的不是同一個人-舊代碼。(對於新接手的來說,他也只有一個屏幕或者一雙眼睛,不能同時去看和理解 P 層和 V 層的代碼。)
舊的 onCreate
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.article_activity_evaluating_result);
......
showLoadingView();
if (isDemo) {
mPresenter.getEvalResultDemo();
} else {
mPresenter.getEvalResult(evalId);
}
}
舊的 showEvalResult 方法
@Override
public void showEvalResult(EvaluatingResultBean bean) {
if (bean != null) {
showContentView();
......
tabLayout.setViewPager(viewPager);
viewPager.addOnPageChangeListener(this);
} else {
showEmptyView();
}
}
舊的 Contract 類
public interface EvaluatingResultContract {
interface View extends DsfBaseView<Presenter> {
void showEvalResult(EvaluatingResultBean bean);
}
interface Presenter extends DsfBasePresenter<View> {
void getEvalResult(int evalId);
void getEvalResultDemo();
}
}
上面看上去是沒有問題。
不過,你能從上面的代碼中知道,showEvalResult 是因爲 getEvalResultDemo 或 getEvalResult 被調用了,然後在 P 層回調過來的嗎?
顯然,答案是否定的。
同時,你也不能知道,showLoadingView 與 showContentView 、 showEmptyView 被成對的調用。
直到,你看到了下面的 P 層實現代碼,回想起剛剛前面的 V 層代碼,才恍然大悟。
getEvalResult 的實現代碼如下:
@Override
public void getEvalResult(int evalId) {
mHttpService.getEvalResult(evalId)
.bindLife(this)
.subscribe(new DsmSubscriberErrorCode<EvaluatingResultBean>() {
@Override
public void onFault(int errorCode, String errorMessage, Throwable e) {
mView.showEvalResult(null);
}
@Override
public void onSuccess(@NonNull EvaluatingResultBean rsp) {
mView.showEvalResult(rsp);
}
});
}
getEvalResultDemo 的實現代碼如下:
@Override
public void getEvalResultDemo() {
mHttpService.getEvalResultDemo()
.bindLife(this)
.subscribe(new DsmSubscriberErrorCode<EvaluatingResultBean>() {
@Override
public void onFault(int errorCode, String errorMessage, Throwable e) {
mView.showEvalResult(null);
}
@Override
public void onSuccess(@NonNull EvaluatingResultBean rsp) {
mView.showEvalResult(rsp);
}
});
}
假設寫 View 的和寫 P 層的不是同一個人-新MVP代碼。
P 入口實現代碼:
public void takeView(EvaluatingResultContract.View view) {
super.takeView(view);
mView.showLoadingView();
if (mIsDemo) {
getEvalResultDemo(mEvalId);
} else {
getEvalResult(mEvalId);
}
}
getEvalResult 的實現代碼
public void getEvalResult(int evalId) {
mHttpService.getEvalResult(evalId)
.bindLife(this)
.subscribe(new DsmSubscriberErrorCode<EvaluatingResultBean>() {
@Override
public void onFault(int errorCode, String errorMessage, Throwable e) {
mView.showToastMessage(errorMessage);
}
@Override
public void onSuccess(@NonNull EvaluatingResultBean rsp) {
mView.showEvalResult(rsp,evalId);
mView.showContentView();
}
});
}
getEvalResultDemo 的實現代碼
public void getEvalResultDemo(int evalId) {
mHttpService.getEvalResultDemo()
.bindLife(this)
.subscribe(new DsmSubscriberErrorCode<EvaluatingResultBean>() {
@Override
public void onFault(int errorCode, String errorMessage, Throwable e) {
mView.showToastMessage(errorMessage);
mView.showEmptyView();
}
@Override
public void onSuccess(@NonNull EvaluatingResultBean rsp) {
mView.showEvalResult(rsp,evalId);
mView.showContentView();
}
});
}
(1、從上面 P 層的實現代碼中,你還是隻有一個屏幕,只有一雙眼鏡。你可以不用記住 V 層的任何代碼,就輕鬆的看到 showLoadingView 與 showContentView 是成對調用的。
啥?不理解?我來把上面的邏輯翻譯成日常用語,你就理解了。
- P層看到用戶剛過來,太着急了,肯定要友好一點,那就先給個 loading 框吧。showLoadingView()。
- 現在用戶不着急了,對於 isDemo 爲 true 的用戶,我們需要請求 getEvalResultDemo 接口數據。對於一般的用戶,我們需要請求 getEvalResult 接口數據。
- 然後是接口數據回來了,當然是渲染到界面上啊。showEvalResult()。
- 數據已經全部渲染好了,還藏着要掖着幹啥子呢。展示給用戶看吧。showContentView()。
- 哎呀呀,網絡竟然出錯了。用戶很懵逼,P 層很着急,V 層啊 V 層,你能幫我通知用戶,是因爲網絡出錯了,我纔拿不到數據的嗎?V 層說,當然沒問題。showToastMessage()。
- 此時 P 層又在想,我得更進一步告訴用戶當前的狀態啊,不然用戶可能一直傻等着呢。(前面已經顯示了 loading)
- 此時 P 層又對 V層 說,V 層啊 V 層。我也沒有什麼數據可以給你的。我們的用戶正在焦急的等待中,你幫忙給用戶畫一個笑臉出來,安慰一下用戶吧。V 層說,這個我最擅長了,放心吧,交給我了。showEmptyView()。
@Override public void takeView(EvaluatingResultContract.View view) { mView.showLoadingView(); if (mIsDemo) { getEvalResultDemo(mEvalId); } else { getEvalResult(mEvalId); } } public void getEvalResult(int evalId) { if(Fault){ mView.showToastMessage(errorMessage); mView.showEmptyView(); }else{ mView.showEvalResult(rsp,evalId); mView.showContentView(); } } public void getEvalResultDemo(int evalId) { if(Fault){ mView.showToastMessage(errorMessage); mView.showEmptyView(); }else{ mView.showEvalResult(rsp,evalId); mView.showContentView(); } }
請認真讀完上面的邏輯。有沒有發現一個事:
P 層除了會指揮 V 層幹這幹那,其它啥也不會
P 層就是個官老爺,P 層就是個“指揮員”。V 層是農民工,V層是施工員,V層是搬磚的勞苦大衆
補充:
P 層有頭腦,會邏輯,有資源(M 層在他手裏啊)。V 層能採集用戶數據,但不知道怎麼把採集到的數據進行處理,也不知道採集到的這些數據需要怎麼保存起來。
也就是說,V 層採集完用戶數據之後,除了向 P 層彙報當前數據情況之外,剩下的就是懵逼等待 P 層新的指示
(2、還記得上面的那個問句嗎?
“不過,你能從上面的代碼中知道,showEvalResult 是因爲 getEvalResultDemo 或 getEvalResult 被調用了,然後在 P 層回調過來的嗎?” -- 答案是否定的。
我來把舊代碼翻譯成日常用語:
- 界面初始化的時候初始化標題欄。initToolBar()
- 然後再設置標題文案。setLeftTitle()
- 用戶可能希望分享啊,我再給用戶設置個分享圖標。setShareIcon()
- 界面初始化日常操作,找 ID 即 bindViewById()
- 上個界面說好要給我一些數據的,我再從上個界面取些數據再說。
- 用戶過來很着急啊,趕緊顯示個 Loading。showLoadingView()
- 現在用戶不着急了,對於 isDemo 爲 true 的用戶,我們需要請求 getEvalResultDemo 接口數據。對於一般的用戶,我們需要請求 getEvalResult 接口數據。
- 此時 V 層突然想到,請求網絡數據這麼複雜的事,我真的不會啊。怎麼辦?怎麼辦?P 層,你會不會?什麼?會啊?好的,P 層,你幫我請求數據吧。P 層回答說,這可是我的看家本領,我當然會啊。V 層,你放心,交給我好了。
- 一秒,兩秒,三秒,P 層把數據已經請求好了。興奮的對大家說,V 層就是笨啊,網絡請求都不會。哈哈,大家看,我一下就把網絡請求搞定了。
- 用戶等了 一秒、兩秒、三秒、四秒、五秒?用戶開始着急了,心裏想什麼情況啊?於是問 V 層說:V層啊,我剛到你們家來,你一直說等待一下再來接待我,請問親愛的 V 層,我到底得等到何年何月啊?
- V 層賠笑到說。我已經讓我們家小P(P層)去國外取一個罕見的寶貝了。等他取回來我就來接待你。
- 此時 V 層一拍腦袋,突然想到剛剛只是叫小P 去取寶貝了,可忘記告訴小P 取好了之後,得及時聯繫自己啊。
- 另一邊,小P 已經取好了寶貝,卻不知道如何通知 V 層。
- V 層使用時時空穿越技術,回到第 8 步。“請求網絡這麼複雜的事,交給 P 層。同時對小P 說,你請求完網絡得通過 showEvalResult 及時告訴我啊”。
- 另一邊,小P 已經去取過了寶貝,然後在想,我該通過什麼方式告訴 V 層呢?然後想起來,V 層剛剛說了,他會 showEvalResult。可是,在去國外的路上我遇上土匪打劫了啊(網絡異常)。那我是否還需要通過 showEvalResult 告訴 V 層呢?P層想啊想啊,卻實在不知道如何辦纔好?
- V 層等啊等啊。用戶急啊急啊。。。
- V 層通過時間穿越技術,回到第 8 步。“請求網絡這麼複雜的事,交給 P 層。同時對小P 說,你請求完網絡得通過 showEvalResult 及時告訴我啊。如果遇上土匪打劫則通過 showEvalResult(null) 告訴我。”。
- 。。。經過 V 層細心的交待,小 P 成功取回了寶貝,並把各種異常情況通過一定的方式通知了 V 層。
- V 層通過回想之前對小 P 的交待,解析出小P 給的數據,根據小P 的數據情況,決定如何去和用戶交互。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.article_activity_evaluating_result); Toolbar toolbar = findViewById(R.id.toolbar); initToolBar(toolbar); mToolbarView.setLeftTitle("評測結果"); mToolbarView.setShareIcon(R.drawable.ic_share_black); mHeaderView = findViewById(R.id.head_view); tabLayout = findViewById(R.id.fea_aspirinTabLayout); viewPager = findViewById(R.id.viewpager); evalId = getIntent().getIntExtra(ModuleArticle.EvaluatingResultActivity.EXTRA_INT_EVAL_ID, 0); boolean isDemo = getIntent().getBooleanExtra(ModuleArticle.EvaluatingResultActivity.EXTRA_BOOLEAN_IS_DEMO, false); showLoadingView(); if (isDemo) { mPresenter.getEvalResultDemo(); } else { mPresenter.getEvalResult(evalId); } } @Override public void showEvalResult(EvaluatingResultBean bean) { if (bean != null) { showContentView(); mHeaderView.bindData(bean); adapter = new EvaluatingResultPagerAdapter(getSupportFragmentManager(), evalId, bean); viewPager.setAdapter(adapter); tabLayout.setViewPager(viewPager); viewPager.addOnPageChangeListener(this); } else { showEmptyView(); } }
其它代碼如下:
public interface EvaluatingResultContract { interface View extends DsfBaseView<Presenter> { void showEvalResult(EvaluatingResultBean bean); } interface Presenter extends DsfBasePresenter<View> { void getEvalResult(int evalId); void getEvalResultDemo(); } }
@Override public void getEvalResult(int evalId) { mHttpService.getEvalResult(evalId) .bindLife(this) .subscribe(new DsmSubscriberErrorCode<EvaluatingResultBean>() { @Override public void onFault(int errorCode, String errorMessage, Throwable e) { mView.showEvalResult(null); } @Override public void onSuccess(@NonNull EvaluatingResultBean rsp) { mView.showEvalResult(rsp); } }); } @Override public void getEvalResultDemo() { mHttpService.getEvalResultDemo() .bindLife(this) .subscribe(new DsmSubscriberErrorCode<EvaluatingResultBean>() { @Override public void onFault(int errorCode, String errorMessage, Throwable e) { mView.showEvalResult(null); } @Override public void onSuccess(@NonNull EvaluatingResultBean rsp) { mView.showEvalResult(rsp); } }); }
就問一個問題:
你只看 Contract 或者只看 V層實現 或者只看 P層實現 能知道上面 1 - 19 的所有邏輯不?
換一個問法,如果我幫你把 V層的類或者 P層的類刪除,你能知道上面 1 - 19 的所有邏輯不?
對於舊代碼,答案顯然是否定的。對於新 MVP 來說,業務控制邏輯都在 P 層寫着呢
結論
舊代碼界面初始化時:
- V 層得告訴 P層:當 V 層發送一個 A 指令時,P 層得通過 A-Result1,2,3 回調給 V 層。當 V 層發送一個 B 指令時,P 層得通過 B-Result1,2,3 回調給 V 層。等等。
- V 層與 P 層的關係不是簡單的命令關係。而是 V 命令 P,P 回調 V ,V 再根據情況繼續命令 P,P 再回調 V 如此交織在一起。
- 等到P、V交織出一個蜘蛛網之後,最終形成穩定態,等待用戶的輸入(用戶的操作)。
V層 P層 邏輯交織
V層是指揮者,可惜只是個三流指揮官。要求 P 層與 V 層精密配合,程序纔可以進入穩定態
另外,如上,舊代碼中 1-5 條顯然不是業務邏輯。卻和 6 - 19 條混合在一起,影響他人閱讀。
新 MVP 界面初始化時:
- P 層問 V層,你會顯示等待框嗎?V 層說,我會。P層說,好的,你去做吧。做完了也不用和我說,我相信你。
- P 層又問 V層,你會畫個笑臉嗎(空數據情況)?V 層說,我會。P層說,好的,你去做吧。做完了也不用和我說,我相信你。
- P 層與 V 層是簡單的命令關係。並且還是基於信任的上下級關係。
- 等到 P 層把自己所有命令發送完成之後,也便形成了穩定態,等待用戶的輸入(用戶的操作)。
一般的,如果你能找到 V層 P層 邏輯交織,算我輸
P層是指揮者,優秀的指揮官,完全信任下屬的指揮官。V 層當個傻白甜,啥也不懂,整個應用程序卻可以進入穩定態