最近因爲感受和同事之間對MVP的理解有所差別,我從新審視了一下MVP的寫法,對目前項目中出現的一些問題進行了思考,創造出一種MVP的變形,我暫且稱它爲MCVP。文末有demo。java
對基礎內容不感興趣的同窗能夠直接跳到MCVP的部分。git
MVP是爲了解決傳統Android開發架構MVC引發的問題的解決方案之一:Activity在MVC架構中既擔當了View又充當了Controller的角色,最後成爲上帝類而且類代碼量爆炸,即便單獨分離出Controller類,狀況仍然沒有獲得多大的改變,只是換了一個上帝而已;這時候MVP被推薦出來,它聽從依賴倒置原則解耦了原來意義上的Activity(View)和Controller(Presenter),經過接口抽象行爲使咱們的UI和業務邏輯分離並得以複用。github
這是一張隨處可見的MVP模式圖,重申一下MVP的基本概念:數據庫
View通常指Activity和Fragment,它們承載用戶的UI界面,和用戶直接交互。服務器
Model指數據源,它是負責獲取和保存數據的對象的集合,而不是特指某一個實體或者叫model的接口,用層的概念來描述它更加合適--Model層。markdown
Presenter是View和Model之間的協調者,負責處理業務邏輯、承接業務邏輯。網絡
View和Presenter之間使用接口調用進行解耦,接口上的方法應該是它們具體行爲的抽象:做爲調用者去調用對方的方法,它會有一個意圖,是 i want to do;而被調用者的暴露方法,就是what i can do的集合。至於對方內部是到底怎麼實現的,調用者絲絕不須要關心。 而Model是否應該使用接口進行抽象呢?我以爲大部分應用都沒這個必要,Model接口的維護成本比較高,帶來的價值也不大,除非你的數據源真的有多個實現,例如在不一樣狀況下使用不一樣的服務器數據庫,能夠考慮用接口解耦。架構
MVP模式大體分爲兩種寫法,被動視圖(Passive View)和監督控制器(Supervising Controller)。被動視圖中View把邏輯和與Model的交互全權交給Presenter,甚至本身的表現也由presenter控制,什麼都不須要管;監督控制器中Presenter只是一個邏輯輔助的職能,View會部分接觸修改到Model的數據,可是我認爲這是被動視圖也沒法避免的,例如列表顯示總要依賴實體類,因此我也更傾向於監督控制器寫法。框架
以上不過是我我的理解,歡迎互相討論。異步
不少小夥伴對MVP甚至是對面向對象的基本原則理解得不夠,因而出現不少奇奇怪怪的問題,包括但不限於如下問題:
和第2點同樣的問題,沒有對View和Presenter的行爲進行抽象,Presenter過多參與控制View的顯示,違反單一職責原則,甚至出現對某些個View控件進行直接操做的狀況。接口只暴露行爲的抽象,具體的操做細節不該該暴露出來。
View的所有點擊事件使用某種惟一參數傳達給Presenter的某個特定方法,在Presenter對參數集中進行判斷執行。出現這種狀況也是沒有對行爲進行抽象。View猶如行屍走肉,徹底沒有i want to do的意圖,就像是把石頭丟入黑洞,至於黑洞裏面發生了什麼事、會發生什麼事?I don't know。好好想想,View-UI做爲與用戶交互的平臺,用戶從UI點擊某個圖標的時候,用戶到底有沒有意圖?那麼UI的事件應不該該有意圖?
上面說了不少奇怪可是不必定會在你項目裏面出現了問題,其實它們不是重點,接下來這個問題我相信應該有出現過在你項目裏,或者你看過的項目裏。
場景與需求:提交訂單的頁面,提交訂單以後會提示能夠升級你的訂單,升級後可以得到更好的售後服務,但要收取少許服務費,而這個訂單是否可以升級,是由後臺動態配置。
流程如圖所示:
代碼大概是這樣的(僞代碼不用太糾結細節,咱們注意看思路便可):
public interface SubmitOrderContract { interface IView { void askUserForUpgrade(CallBack<Boolean> callback); void onSubmitSucceed(); void onSubmitFailed(); } interface IPresenter { /** * 提交訂單 */ void submitOrder(SubmitOrderEntity order); } } 複製代碼
public class SubmitOrderActivity extends AppCompatActivity implements SubmitOrderContract.IView { ... @Override public void askUserForUpgrade(final CallBack<Boolean> callback) { final InquiryDialog dialog = new InquiryDialog(this); dialog.setOnResultListener(new InquiryDialog.OnResultListener({ @Override public void onResult(boolean result) { dialog.dismiss(); callback.onResult(result); } }); dialog.show(); } ... } 複製代碼
public class SubmitOrderPresenter implements SubmitOrderContract.IPresenter { ... @Override public void submitOrder(final SubmitOrderEntity order) { mApi.submitOrder(order, new HttpApi.HttpListener<SubmitResultEntity>() { @Override public void onCallBackOk(SubmitResultEntity result) { if (result.canUpDate) { mView.askUserForUpgrade(new CallBack<Boolean>() { @Override public void onResult(Boolean p) { submitOnUpgrade(p, order); } }); } else { mView.onSubmitSucceed(); } } }); } private void submitOnUpgrade(boolean isContinue, SubmitOrderEntity order) { if (!isContinue) { mView.onSubmitFailed(); return; } mApi.submitOrderForUpgrade(order, new HttpApi.HttpListener<SubmitResultEntity>() { @Override public void onCallBackOk(SubmitResultEntity submitResultEntity) { mView.onSubmitSucceed(); } }); }} 複製代碼
這發生了兩個問題:
針對這種特殊場景,我給出的解決方案是:
MCVP MCVP MCVP MCVP MCVP
即 Model-CallbackView-Presenter。 看到CallbackView這個詞,聰明的同窗可能已經猜出了答案,是的,意思是一個會迴應的View。MCVP是MVP的一個特殊變體,能夠隨時運用到你的項目裏。
在Presenter這個角色的視角中,詢問用戶是否升級訂單,不過是一個異步獲取數據的過程而已,咱們徹底能夠把View當作是一個數據源,仿照網絡請求的方式等待用戶選擇結果返回。
View只須要詢問用戶是否確認而後告訴Presenter就足夠了,甚至不須要知道Presenter要它詢問用戶確認些什麼東西。就像菜販子和顧客的關係,菜販只要知道顧客須要什麼蔬菜,而不須要知道顧客買這個瓜回去幹些什麼,徹底的解耦關係。
當咱們選擇這種方案,傳遞給View的入參變成了帶有泛型的回調接口Callback,View只知道咱們須要它回答什麼(泛型T),它經過Callback接口回調過去便可。
MCVP徹底避免了業務邏輯的斷裂,保持了流程的連貫,presenter對於邏輯流程徹底可知,提 高了邏輯可讀性,類中的業務邏輯方法內聚性更加高,你可愛的業務邏輯終於不須要再在View和Presenter的實現上跳來跳去了,剛接手代碼的人也簡單清楚邏輯流的走向,維護性也提升了;並且徹底符合被動視圖的寫法,View也不會所以接觸到任何實體類。解決了上面所述的兩個問題。
看一下MCVP的代碼:
public interface SubmitOrderContract { interface IView { void askUserForUpgrade(CallBack<Boolean> callback); void onSubmitSucceed(); void onSubmitFailed(); } interface IPresenter { /** * 提交訂單 */ void submitOrder(SubmitOrderEntity order); } } 複製代碼
public class SubmitOrderActivity extends AppCompatActivity implements SubmitOrderContract.IView { ... @Override public void askUserForUpgrade(final CallBack<Boolean> callback) { final InquiryDialog dialog = new InquiryDialog(this); dialog.setOnResultListener(new InquiryDialog.OnResultListener({ @Override public void onResult(boolean result) { dialog.dismiss(); callback.onResult(result); } }); dialog.show(); } ... } 複製代碼
public class SubmitOrderPresenter implements SubmitOrderContract.IPresenter { ... @Override public void submitOrder(final SubmitOrderEntity order) { mApi.submitOrder(order, new HttpApi.HttpListener<SubmitResultEntity>() { @Override public void onCallBackOk(SubmitResultEntity result) { if (result.canUpDate) { mView.askUserForUpgrade(new CallBack<Boolean>() { @Override public void onResult(Boolean p) { submitOnUpgrade(p, order); } }); } else {mView.onSubmitSucceed(); } } }); } private void submitOnUpgrade(boolean isContinue, SubmitOrderEntity order) { if (!isContinue) { mView.onSubmitFailed(); return; } mApi.submitOrderForUpgrade(order, new HttpApi.HttpListener<SubmitResultEntity>() { @Override public void onCallBackOk(SubmitResultEntity submitResultEntity) { mView.onSubmitSucceed(); } }); } } 複製代碼
能夠看到,全部業務流程都在submitOrder()中,沒有跳出這個方法,submitOrder()很是忠實地完成了它提交訂單的任務,沒有再假手於人(View)了。
有同窗說出現了回調地獄啊~那很好解決,相信你們都知道解決方法,Rxjava、協程...它們均可以解決。因爲篇幅緣由,RxJava代碼的部分能夠看文末傳送門看demo。
MCVP有它的優勢,但我也認可它也有本身的缺點:
MCVP只不過是CallbackView與MVP的結合,這個思想徹底能夠移植到其餘模式上。除了個人舉例,它應該會有更多寫法,例如從View方法返回Callback而不是Presenter提供Callback,我也會繼續繼續思考優化MCVP,期待它可以在更多場景發揮做用,使咱們的代碼結構變得更加好。
謝謝你的觀看!
週末看比卡超去吧~!