什麼是MVP?—— 關於標準MVP與把P層當網絡層使用的區別

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 是成對調用的。

啥?不理解?我來把上面的邏輯翻譯成日常用語,你就理解了。 

  1. P層看到用戶剛過來,太着急了,肯定要友好一點,那就先給個 loading 框吧。showLoadingView()。
  2. 現在用戶不着急了,對於 isDemo 爲 true 的用戶,我們需要請求 getEvalResultDemo 接口數據。對於一般的用戶,我們需要請求 getEvalResult 接口數據。
  3. 然後是接口數據回來了,當然是渲染到界面上啊。showEvalResult()。
  4. 數據已經全部渲染好了,還藏着要掖着幹啥子呢。展示給用戶看吧。showContentView()。
  5. 哎呀呀,網絡竟然出錯了。用戶很懵逼,P 層很着急,V 層啊 V 層,你能幫我通知用戶,是因爲網絡出錯了,我纔拿不到數據的嗎?V 層說,當然沒問題。showToastMessage()。
  6. 此時 P 層又在想,我得更進一步告訴用戶當前的狀態啊,不然用戶可能一直傻等着呢。(前面已經顯示了 loading)
  7. 此時 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 層回調過來的嗎?” -- 答案是否定的。 

我來把舊代碼翻譯成日常用語:

  1. 界面初始化的時候初始化標題欄。initToolBar()
  2. 然後再設置標題文案。setLeftTitle()
  3. 用戶可能希望分享啊,我再給用戶設置個分享圖標。setShareIcon()
  4. 界面初始化日常操作,找 ID 即 bindViewById()
  5. 上個界面說好要給我一些數據的,我再從上個界面取些數據再說。
  6. 用戶過來很着急啊,趕緊顯示個 Loading。showLoadingView()
  7. 現在用戶不着急了,對於 isDemo 爲 true 的用戶,我們需要請求 getEvalResultDemo 接口數據。對於一般的用戶,我們需要請求 getEvalResult 接口數據。
  8. 此時 V 層突然想到,請求網絡數據這麼複雜的事,我真的不會啊。怎麼辦?怎麼辦?P 層,你會不會?什麼?會啊?好的,P 層,你幫我請求數據吧。P 層回答說,這可是我的看家本領,我當然會啊。V 層,你放心,交給我好了。
  9. 一秒,兩秒,三秒,P 層把數據已經請求好了。興奮的對大家說,V 層就是笨啊,網絡請求都不會。哈哈,大家看,我一下就把網絡請求搞定了。
  10. 用戶等了 一秒、兩秒、三秒、四秒、五秒?用戶開始着急了,心裏想什麼情況啊?於是問 V 層說:V層啊,我剛到你們家來,你一直說等待一下再來接待我,請問親愛的 V 層,我到底得等到何年何月啊?
  11. V 層賠笑到說。我已經讓我們家小P(P層)去國外取一個罕見的寶貝了。等他取回來我就來接待你。
  12. 此時 V 層一拍腦袋,突然想到剛剛只是叫小P 去取寶貝了,可忘記告訴小P 取好了之後,得及時聯繫自己啊。
  13. 另一邊,小P 已經取好了寶貝,卻不知道如何通知 V 層。
  14. V 層使用時時空穿越技術,回到第 8 步。“請求網絡這麼複雜的事,交給 P 層。同時對小P 說,你請求完網絡得通過 showEvalResult 及時告訴我啊”。
  15. 另一邊,小P 已經去取過了寶貝,然後在想,我該通過什麼方式告訴 V 層呢?然後想起來,V 層剛剛說了,他會 showEvalResult。可是,在去國外的路上我遇上土匪打劫了啊(網絡異常)。那我是否還需要通過 showEvalResult 告訴 V 層呢?P層想啊想啊,卻實在不知道如何辦纔好?
  16. V 層等啊等啊。用戶急啊急啊。。。
  17. V 層通過時間穿越技術,回到第 8 步。“請求網絡這麼複雜的事,交給 P 層。同時對小P 說,你請求完網絡得通過 showEvalResult 及時告訴我啊。如果遇上土匪打劫則通過 showEvalResult(null) 告訴我。”。
  18. 。。。經過 V 層細心的交待,小 P 成功取回了寶貝,並把各種異常情況通過一定的方式通知了 V 層。
  19. 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 層寫着呢

結論


舊代碼界面初始化時:

  1. V 層得告訴 P層:當 V 層發送一個 A 指令時,P 層得通過 A-Result1,2,3 回調給 V 層。當 V 層發送一個 B 指令時,P 層得通過 B-Result1,2,3 回調給 V 層。等等。
  2. V 層與 P 層的關係不是簡單的命令關係。而是 V 命令 P,P 回調 V ,V 再根據情況繼續命令 P,P 再回調 V 如此交織在一起。
  3. 等到P、V交織出一個蜘蛛網之後,最終形成穩定態,等待用戶的輸入(用戶的操作)。

V層 P層 邏輯交織

V層是指揮者,可惜只是個三流指揮官。要求 P 層與 V 層精密配合,程序纔可以進入穩定態

另外,如上,舊代碼中 1-5 條顯然不是業務邏輯。卻和 6 - 19 條混合在一起,影響他人閱讀。 


新 MVP 界面初始化時:

  1. P 層問 V層,你會顯示等待框嗎?V 層說,我會。P層說,好的,你去做吧。做完了也不用和我說,我相信你。
  2. P 層又問 V層,你會畫個笑臉嗎(空數據情況)?V 層說,我會。P層說,好的,你去做吧。做完了也不用和我說,我相信你。
  3. P 層與 V 層是簡單的命令關係。並且還是基於信任的上下級關係。
  4. 等到 P 層把自己所有命令發送完成之後,也便形成了穩定態,等待用戶的輸入(用戶的操作)。

一般的,如果你能找到 V層 P層 邏輯交織,算我輸

P層是指揮者,優秀的指揮官,完全信任下屬的指揮官。V 層當個傻白甜,啥也不懂,整個應用程序卻可以進入穩定態


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章