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