编辑推荐: |
本文来自于简书,本文主要介绍了MVP存在的问题和解决方案的两种思路,MVCPI的代码结构等。
|
|
1、MVC的主要问题
前面说到,很多人抱怨采用MVC模式使得Controller变得很臃肿,我相信,Controller变得臃肿是事实,但其归结于采用MVC模式是不正确的,这个锅不应该由MVC来背,因为,这个论点会导致我们走向错误的方向从而无法发现MVC真正的问题所在。为什么这么说呢,那是因为在本人了解到的很多情况下,大家并没有正确理解MVC框架模式,如采用前文中第一种模式,自然会使得Controller臃肿,但是如果采用第二种模式,Controller的代码和逻辑也会非常清晰,至少不至于如此多的抱怨。因此如果只是想解决Controller臃肿的话,MVC就够了,毋庸质疑。那MVC的问题是什么呢?我想只有深刻的理解了这个问题,我们才有必要考虑是否需要引入新的框架模式,以及避免新的模式中可能出现的问题。
View强依赖于Model是MVC的主要问题。由此导致很多控件都是根据业务定制,从Android的角度来看,原本可以由一个通用的layout就能实现的控件,由于要绑定实体模型,现在必须要自定义控件,这导致出现大量不必要的重复代码。因此有必要将View和Model进行解耦,而MVP的主要思想就是解耦View和Model。由此引入MVP就显得很自然。
在Android中传统的MVC(第一种模式)
改进后的MVC(第二种模式)
2、MVP存在的问题
。比如,上下文丢失问题,生命周期问题,内存泄露问题以及大量的自定义接口,回调链变长等问题。可以归纳为:
业务复杂时,可能使得Activity变成更加复杂,比如要实现N个IView,然后写更多个模版方法。
业务复杂时,各个角色之间通信会变得很冗长和复杂,回调链过长。
Presenter处理业务,让业务变得很分散,不能全局掌握业务,很难去回答某个业务究竟是在哪里处理的。
用Presenter替代Controller是一个危险的做法,可能出现内存泄漏,生命周期不同步,上下文丢失等问题。
以下面的需求来看几个具体的示例: 详情按钮的展示需要服务端下发标记位控制,展示时点击需要请求一个服务,服务返回时toast提示用户
上面的Presenter就缺少了上下文对象,导致Presenter层连个Toast都弹不了。
解决方案有两种思路:
一个是为Presenter增加生命周期方法,在Fragment的生命周期方法里调用Presenter对应的生命周期函数,但这就让Presenter看起来像Fragment的孙子;另外一个就是承认Presenter其实不太合适承担Controller的职责,从而提供接口给外部处理;
1、在构造函数中传入一个Context
2、使用接口回调
个人认为接口回调的方案较好,不过当业务特别复杂的时候,Activity会显的有点乱。
3、MVCPI
由于前面的分析,MVP参考实现并不是万能的,携程酒店并没有完全采用参考实现方案,而是结合自身的实践经验思考之后设计出来的扩展方案。我们主要考虑了一下的几个问题:
如何定义View接口?
如何定位Presenter ?
如何对待Controller?
如何解决长长的回调链?
通过对上述问题的思考,提出对应的解决方法,规避前面论述的各种问题,形成了携程酒店的MVCPI框架模式,并在多个业务场景运行,取得了较为满意的效果。下面,详细介绍MVCPI模式。
3.1、 IView
和Android 参考实现不一样的是,我们并没有采用强类型的接口作为表达View的方式,而是采用弱类型的接口来定义View。具体定义方式如下:
上面的接口简洁的描述了作为业务控件的View需要具备的子控间ID,并不需要具体的实现类。因此也不需要Activity去实现这个接口,只需要在layout中申明这几个ID的即可,极大的简化了代码。
3.2、 Presenter
与参考实现的定位不一样,我们认为由Presenter取代Controller并不是一个好的做法,Presenter应是Controller的补充,主要起到View和Model解耦和数据绑定的作用,所负责的控件的上的业务还是有Controller决定如何去处理。另外setView接受的参数是一般的View,而非一个接口类型,内部根据IView定义的ID去查找子控件。如下:
3.3、 Interactor
Interactor是我们定义出来的扩展元素,在MVP和MVC中都没有对应的角色。为了阐述它的含义,我们先来看看两个非常常见的场景。
回调链过长
在前面介绍过,Presenter自定义接口是很多候选方案中较为合理的选择,但相比MVC而言,MVP更容易出现如上图的一种调用和回调关系(甚至更长)。维护这种回调链通常来说是一件非常头痛的事情,从View的角度来看,很难知道某个事件到最后究竟完成了什么业务,Acitivity也不知道到要装配哪些回调。某个未知的新需求可能需要将该链条上的每个环节都增加回调。
下面来是另外一种场景,大家可以脑补一下采用上面的回调方案,回调链会是什么情况。
交互集中型界面
在该界面有几个特点:
几十种动态交互需求,
分布于不同的模块
分布于不同深度的嵌套层次中
经过大量版本迭代后,无论产品经理,研发或者测试,都不清楚到底有哪些需求,业务逻辑是什么,写在什么地方等等……
上述两个场景可以得出两个结论:
排查问题非常耗时
增加功能成本高,容易引致其他问题
为了解决上述两个比较棘手的问题,我们引入了Interactor,用于描述整个界面的交互,一举解决上述两个问题。我们认为交互模型是一个功能模块的重要逻辑单元,相对于实体模型来说,交互模型更加抽象,在大多数的情况,并不能引起大家的注意,但它确实是如实体一样的存在,正是因为没有对交互进行系统的描述,才导致上面两种突出的问题。尽管抽象,但是交互模型本质非常简单,它有着和实体模型有相似的结构,示例如下:
通过对界面整体分析后,我们建立如上的交互模型,所有的交互都在交互模型进行注册,由交互模型统一管理,进而可以对整个界面的交互进行宏观把控;然后在页面的所有元素中共享同一个交互模型,进而各个元素不再需要自定义接口和避免建立回调链。最后由Controller负责组装,进一步加强Controller的控制能力。
3.4、 MVCPI全貌
最后,整体介绍一下MVCPI的代码结构
1、首先定义整个界面中有哪些用户交互,本例中就一个详情按钮交互
2、Presenter构造时需要传入交互模型,内部定义了IView接口,传入的View中需要包含它定义的ID的控件,在bindData时,详情按钮的点击不是通过匿名内部类去处理,而是直接引用交互模型中定义的mDetail
3、Controller负责界面各个元素(包括交互模型)的初始化和装配 |