內聚代碼提升邏輯可讀性,用MCVP接續你的大邏輯

最近因爲感受和同事之間對MVP的理解有所差別,我從新審視了一下MVP的寫法,對目前項目中出現的一些問題進行了思考,創造出一種MVP的變形,我暫且稱它爲MCVP。文末有demo。java

MVP

對基礎內容不感興趣的同窗能夠直接跳到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甚至是對面向對象的基本原則理解得不夠,因而出現不少奇奇怪怪的問題,包括但不限於如下問題:

  1. 建立出IModel接口,爲每個不一樣的IPresenter接口實現一個Model類,IPresenter和IModel經過Contract關聯一塊兒,這樣不只出現了不少Model類,也一併出現了很是多的冗餘代碼。正如我上面所說,Model其實是一個集合,一個Model層的概念。這張圖更加能說明狀況。

2. IView接口和IPresenter接口之間getter setter具體參數的方法過多,違反迪米特原則,通俗地說,就是暴露太多了。辛辛苦苦寫幾個接口來作MVP,而後加了N個getter setter,是想要把這個接口看成java bean來使用嗎?MVP接口的基本就是對行爲的抽象,一堆getter setter有抽象可言咩?

  1. 和第2點同樣的問題,沒有對View和Presenter的行爲進行抽象,Presenter過多參與控制View的顯示,違反單一職責原則,甚至出現對某些個View控件進行直接操做的狀況。接口只暴露行爲的抽象,具體的操做細節不該該暴露出來。

  2. View的所有點擊事件使用某種惟一參數傳達給Presenter的某個特定方法,在Presenter對參數集中進行判斷執行。出現這種狀況也是沒有對行爲進行抽象。View猶如行屍走肉,徹底沒有i want to do的意圖,就像是把石頭丟入黑洞,至於黑洞裏面發生了什麼事、會發生什麼事?I don't know。好好想想,View-UI做爲與用戶交互的平臺,用戶從UI點擊某個圖標的時候,用戶到底有沒有意圖?那麼UI的事件應不該該有意圖?

  1. Presenter知曉了Android框架的東西。Intent的數據獲取和傳遞、onActivityResult()的返回處理,這些都是Android框架的部分,應該交回View去處理。往大說是Presenter不要依賴Android環境便於測試,往小說是Intent數據的定義、startActivity()都是activity、fragment的事,你一個Presenter摻和啥呢?

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();
            }
        });
    }}

複製代碼
發生的問題

這發生了兩個問題:

  1. 業務邏輯流割裂。咱們的業務代碼因爲須要View的彈框詢問而斷裂成submitOrder()和submitUpgradeOrder()兩個方法,致使邏輯不連貫。

  1. 邏輯泄露。在Presenter的視角來看,提交訂單的一系列流程並非Presenter本身可知可控的,雖然submitUpgradeOrder() 是提交訂單流程的一步,可是Presenter本身徹底不知道何時會被觸發,submitUpgradeOrder()的觸發邏輯被把握在View手裏,View把握了業務邏輯!

解決方案

針對這種特殊場景,我給出的解決方案是:

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有它的優勢,但我也認可它也有本身的缺點:

  1. Callback只能使用一次,並且要保證它不會被調用兩次,否則你後面的邏輯就走兩遍了,但只要記住這個狀況存在,基本不會出幺蛾子。
  2. 因爲Callback的存在,View須要持有Callback這個對象,存放成全局變量(一個便可)或者每次建立你的UI(就像例子中的dialog那樣),不過這我以爲其實不算是個問題,全局變量只是個小小的雜質,不影響它帶來的優勢。

MCVP只不過是CallbackView與MVP的結合,這個思想徹底能夠移植到其餘模式上。除了個人舉例,它應該會有更多寫法,例如從View方法返回Callback而不是Presenter提供Callback,我也會繼續繼續思考優化MCVP,期待它可以在更多場景發揮做用,使咱們的代碼結構變得更加好。

去GitHub看Demo

謝謝你的觀看!

週末看比卡超去吧~!

相關文章
相關標籤/搜索