什么是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 层当个傻白甜,啥也不懂,整个应用程序却可以进入稳定态


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