内容简介:最近由于感觉和同事之间对MVP的理解有所差异,我重新审视了一下MVP的写法,对目前项目中出现的一些问题进行了思考,创造出一种MVP的变形,我暂且称它为MCVP。文末有demo。对基础内容不感兴趣的同学可以直接跳到MCVP的部分。这是一张随处可见的MVP模式图,重申一下MVP的基本概念:
最近由于感觉和同事之间对MVP的理解有所差异,我重新审视了一下MVP的写法,对目前项目中出现的一些问题进行了思考,创造出一种MVP的变形,我暂且称它为MCVP。文末有demo。
MVP
对基础内容不感兴趣的同学可以直接跳到MCVP的部分。
这是一张随处可见的MVP模式图,重申一下MVP的基本概念:
View一般指Activity和Fragment,它们承载用户的UI界面,和用户直接交互。
Model指数据源,它是负责获取和保存数据的对象的集合,而不是特指某一个实体或者叫model的接口,用层的概念来描述它更加合适-- Model层 。
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甚至是对面向对象的基本原则理解得不够,于是出现很多奇奇怪怪的问题,包括但不限于以下问题:
- 创建出IModel接口,为每一个不同的IPresenter接口实现一个Model类,IPresenter和IModel通过Contract关联一起,这样不仅出现了很多Model类,也一并出现了非常多的冗余代码。正如我上面所说,Model实际上是一个集合,一个Model层的概念。这张图更加能说明情况。
2. IView接口和IPresenter接口之间getter setter具体参数的方法过多,违反迪米特原则,通俗地说,就是暴露太多了。辛辛苦苦写几个接口来做MVP,然后加了N个getter setter,是想要把这个接口当作java bean来使用吗?MVP接口的基本就是对行为的抽象,一堆getter setter有抽象可言咩?
-
和第2点一样的问题,没有对View和Presenter的行为进行抽象,Presenter过多参与控制View的显示,违反单一职责原则,甚至出现对某些个View控件进行直接操作的情况。接口只暴露行为的抽象,具体的操作细节不应该暴露出来。
-
View的全部点击事件使用某种唯一参数传达给Presenter的某个特定方法,在Presenter对参数集中进行判断执行。出现这种情况也是没有对行为进行抽象。View犹如行尸走肉,完全没有i want to do的意图,就像是把石头丢入黑洞,至于黑洞里面发生了什么事、会发生什么事?I don't know。好好想一想,View-UI作为与用户交互的平台,用户从UI点击某个图标的时候,用户到底有没有意图?那么UI的事件应不应该有意图?
- 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();
}
});
}}
复制代码
发生的问题
这发生了两个问题:
- 割裂。我们的业务代码由于需要View的弹框询问而断裂成submitOrder()和submitUpgradeOrder()两个方法,导致逻辑不连贯。在Presenter的视角来看,提交订单的一系列流程并不是Presenter自己可知可控的,虽然submitUpgradeOrder() 是提交订单流程的一步,但是自己它似乎完全不知道什么时候会被触发,至少从presenter代码来看是这样的。
- 不符合被动视图的写法。以被动视图的思路来看,View只需要询问用户是否确认然后告诉Presenter就足够了,甚至不需要知道Presenter要它询问用户确认些什么东西。就像菜贩子和顾客的关系,菜贩只要知道顾客需要什么蔬菜,而不需要知道顾客买这个瓜回去干些什么,完全的解耦关系。
解决方案
针对这种特殊场景,我给出的解决方案是:
MCVP MCVP MCVP MCVP MCVP
即 Model-CallbackView-Presenter。 看到CallbackView这个词,聪明的同学可能已经猜出了答案。MCVP是MVP的一个特殊变体,可以随时运用到你的项目中。
在Presenter这个角色的视角中,询问用户是否升级订单,不过是一个异步获取数据的过程而已,我们完全可以把View看成是一个数据源,仿照网络请求的方式等待用户选择结果返回。
当我们选择这种方案,传递给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有它的优点,但我也承认它也有自己的缺点:
- Callback只能使用一次,而且要保证它不会被调用两次,不然可能发生某些问题,只要知道这个情况存在,基本不会出幺蛾子。
- 由于Callback的存在,View需要持有Callback这个对象,存放成全局变量(一个即可)或者每次创建你的UI(就像例子中的dialog那样),不过这我觉得其实不算是个问题,全局变量只是个小小的杂质,不影响它带来的优点。
MCVP只不过是CallbackView与MVP的结合,这个思想完全可以移植到其他模式上。除了我的举例,它应该会有更多写法,例如从View方法返回Callback而不是Presenter提供Callback,我也会继续继续思考优化MCVP,期待它能够在更多场景发挥作用,使我们的代码结构变得更加好。
谢谢你的观看!
周末看比卡超去吧~!
以上所述就是小编给大家介绍的《内聚代码提高逻辑可读性,用MCVP接续你的大逻辑》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 分布式系统关注点:高内聚低耦合详解
- Flutter高内聚组件怎么做?闲鱼闲鱼打造开源高效方案!
- 软件架构基础(三): 什么是好的模块化代码?高内聚、低耦合如何衡量?
- centos创建逻辑卷和扩容逻辑卷
- AI「王道」逻辑编程的复兴?清华提出神经逻辑机,已入选ICLR
- 逻辑式编程语言极简实现(使用C#) - 1. 逻辑式编程语言介绍
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning XML with DOM and Ajax
Sas Jacobs / Apress / 2006-06-05 / USD 39.99
Don't waste time on 1,000-page tomes full of syntax; this book is all you need to get ahead in XML development. Renowned web developer Sas Jacobs presents an essential guide to XML. Beginning XML with......一起来看看 《Beginning XML with DOM and Ajax》 这本书的介绍吧!