说说Android的MVP模式

2023-05-16

更多精彩博客

安卓应用开发是一个看似容易,实则很难的一门苦活儿。上手容易,看几天Java,看看四大组件咋用,就能整出个不太难看的页面来。但是想要做好,却是很难。系统框架和系统组件封装了很多东西,开发者弄几个Activity,用LinearLayout把布局组合在一起,添加点事件监听,一个应用就成型了。红海竞争,不管多么复杂的UX和业务逻辑都是一个月快速上线,二周一个迭代,领导和产品早上改需求,晚上改设计,再加上产品经理和设计师都按照iOS来设计,这一系列原因导致很多安卓应用不但体验差,不稳定,性能低,而且内部代码相当之混乱,即使BAT也是如此。

反观国外市场(谷歌应用市场)上面的大部分应用都还是比较好的,表现在符合安卓设计规范,性能和稳定上表现不俗,体验上更符合安卓系统,而且会发现他们的代码也是很有设计思想的。GitHub上面的很多安卓开源项目也都是源自国外的优秀开发者以及他们的项目。

安卓应用也是软件,代码结构合理,层次清晰不但容易维护而且还容易做自动化测试和单元测试,这是开发者的美好愿望,也是提升效率的必然之路。

安卓由于系统架构特性,UI组件Activity中融合了View的处理,事件处理和逻辑处理,随着业务的越来越复杂,导致Activity也越来越雍肿,几千行的Activity随处可见,Fragment也不能解决问题,千行以上的Fragment也不在少数,这个时候就完全不要谈什么可维护性,可测试性了。能完成需求就算高手了。

MVP便应运而生,就来解决这些问题的。

什么是MVP模式

MVP是针对有GUI存在的应用程序,比如像安卓,像水果以及PC的客户端软件中用以划分组织代码的一种设计模式,是由MVC模式升级演进出来的,目的在于,对于GUI层来说,把UI展示与逻辑分开。

  • Model – 为UI层提供的数据,或者保存UI层传下来的数据
  • View – 单纯的展示数据,响应用户操作并都转发给Presenter来做具体的处理
  • Presenter – 逻辑控制层,从Model处取数据,运算和转化,最后用View来展示;并处理View传过来的用户事件,并做处理

需要注意的是MVP仅用于应用中的GUI部分,它并不是整个应用的架构方式。一个应用的主要的架构应该包括基础组件,业务逻辑层和GUI展示层,而MVP仅是用于展示层的设计模式。另外,它是一个方法论的东西,没有固定的实现方式,只要能体现出它的方法就可以算是MVP。

虽然是方法论,但是也有一些指导性的原则来约束实现:

  • Model与View不能直接通信,只能通过Presenter
  • Presenter类似于中间人的角色进行协调和调度
  • Model和View是接口,Presenter持有的是一个Model接口和一个View接口
  • Model和View都应该是被动的,一切都由Presenter来主导
  • Model应该把与业务逻辑层的交互封装掉,换句话说Presenter和View不应该知道业务逻辑层
  • View的逻辑应该尽可能的简单,不应该有状态。当事件发生时,调用Presenter来处理,并且不传参数,Presenter处理时再调用View的方法来获取。

从这里可以看的出来,其实,MVP的目的就是把GUI的逻辑都集中在Presenter层,又把View层和Model与其用接口分离,让View尽可能的简单,这样可以加强移植性。因为View层是肯定不能移植的,不同的平台GUI的窗口部件肯定不一样,Model也是不太好移植的,因为每个平台的IO也都是不一样的。但是,MVP中的P肯定是可以移植的,因为它里面只有逻辑,且View和Model都是接口,所以很容易移植。同时,因为View和Model都是接口,这个Presenter也非常好测试,只要实现一个View的接口和Model的接口,就可以单独的测试Presenter了。

严格来讲,View只是被动的显示,提供方法由Presenter来调用,数据等都是由Presenter来提供,内部不能任何的逻辑与状态,逻辑和状态都应该是在Presenter中。UI事件发生时,调用Presenter的方法来处理,不能传参数,也不能有返回值,在Presenter中处理后再调用View来更新数据和状态。

MVP与MVC的区别

MVC之中逻辑是放在了Model里,Controller负责桥接View和Model,View发生变化时通知Controller,Controller再通知Model,Model进行逻辑处理,更新数据,然后通知View来刷新。可以看到MVC中三者之间都有联系,如果处理不好,或者当View比较复杂时,三者之间都会双向关联。MVC在命令行应用,以及WEB中有大量的应用,但对于客户端(PC和移动端)的GUI应用,MVC往往解决不了复杂性,移植性上以及可测试性上也没有优势。

MVP的改进在于:

  • 逻辑放在Presenter中
  • View和Model抽象成为接口

这样就带了二个好处:

  • 代码更加容易移植
  • 代码更加容易加入Unit Testing

如何在安卓中实践MVP

MVP是一个方法论的东西,也就是没有任何固定的具体的实现形式,只要能够把View跟Model解除联系,把逻辑都放在Presenter中,那么就能算得上是MVP,一些具体的实践的指导性原则:

  • View是一个接口,负责被动的把处理好的数据显示出来
  • Model也是一个接口,负责获取数据和存储数据
  • View调用Presenter处理用户事件也是一个接口,称为事件Delegate
  • Presenter持有的是View的接口和Model接口

安卓的Activity是一个比较奇葩的角色,在MVP中,既可以用作V,因为一个应用的根布局总是由Activity来创建的。当然也可以当作P,因为Activity是一个应用的入口,也是出口,再加上一些关键的系统事件也都是通过Activity的方法来通知的(比如configChange, saveInstance)。其实,都可以。因为MVP是方法论,并没有固定的形式,只要是把数据处理的逻辑都封装在Presenter里,让其去控制View和Model,让Activity来承担View还是Presenter,其实都可以。

MVP不是银弹,仅是展示层的一种范式而已

最重要的一点就是要明白,MVP不会拯救你的应用,不要以为使用了MVP就能让代码更容易维护,更少的Bug,添加新功能会更容易。MVP仅是GUI层的一种编程范式而已,且因为它是方法论的东西,对实现方式并没有固定的形式,所以会被滥用,如果没有深刻理解MVP的思想,更加会导致灾难性的结果。

软件,移动应用也不例外,如果功能简单,业务简单,那么代码怎么写其实也都无大碍,但当功能越来越多,业务越来越复杂的时候,就必须要采取必要的方法来应对复杂度和软件的可开发性,可维护性。比如,说的夸张一点,一个helloworld式的应用,你怎么写都可以。但当功能复杂到一个Activity几千行代码的时候,你再怎么MVP,MVC或者MVVM都不能解决问题,再怎么把Activity当成P或者当成V都没有用。

要知道MVP仅是解决GUI应用程序中展示层的问题,并且它带来的最大的好处是方便测试和移植,因为逻辑都在P里面,P持有的又仅是View和Model的接口,所以P是可测试的,Mock一个View的实现,和Mock一个Model的实现,就可以完全脱离平台和框架的限制来自由的测试P。同样,移到一个新的框架和平台后,只需要实现View和Model就可以了,P是不需要改变的。

分层和模块化才是解决应用越来越复杂之道

分层

所谓分层,也就是应用程序的架构方法,把应用程序分成好多层,可以参考Bob大叔的The Clean Architecture。

至少应该分层三层,最底层是平台适配层,把用到的平台的组件,控件,工具,比如UI组件,数据库等等,进行封装;中间层就是业务层,就是你应用的核心的业务逻辑,或者说你的应用解决了用户什么样子的问题,这一层是不会随着平台和UI的改变而改变的。比如新闻阅读类,那么从服务器拉取数据,解析数据,缓存数据,为上层提供数据这些事情都属于业务层;最上面就是展示层或者叫做UI层。展示层是可以调用业务层的方法和数据。这样分层,可以让展示层只是负责与用户交互,展示业务数据,展示层会变得简单很多,同时业务层因为不涉及具体的平台和UI的细节,就非常容易移植,当移植到新平台或者要做UI改版也是非常容易做的。

模块化

另外一个就是模块化,其实这是软件开发的一个非常基本的方法,也是非常有用的一个方法。模块划分的方法非常简单就是按照功能来划分。让模块处理好自己的事情,暴露统一的接口给外部,定义好输入与输出。输入就以参数和方法形式暴露,输出最好以Delegate方式,这样能把耦合降到最低。再由一个统一的顶层类来管理各个模块,顶层直接调用各模块,各模块通过Delegate方式来回调管理者。

对于业务层,模块化相对比较容易,因为这里并不涉及UI和平台的特性,业务层都应该是独立的,可移植的,全都是自己写的类。

但对于展示层,通常没有那么的容易,因为有平台的限制。比如说安卓,根布局必须由Activity来创建。首先,模块的划分也要以功能为界限。然后,就是Activity的布局,要把布局按功能区域来管理,然后把每个功能模块的top container传给模块,具体内部如何布局,如何填充数据,就由模块自己负责。Activity就起管理各个模块的作用。再有,模块间的通信,可以都通过Activity来,比如模块1有模块2的入口按扭,但是模块1与模块2之间没有交集,这个时候的处理方式就是模块1Delegate给Activity,然后Activity再调用模块2来显示和隐藏。如果模块多到Activity的管理工作也变得庞大复杂时就要拆出子Controller来管理模块,也就是三层,甚至还可以四层。模块的原则就是做好封装,让外层管理变得简单,这样外层管理的复杂度就会降下来,就好比公司人员的组织架构一样。


1
2
3
4
5
  
<LinearLayout>
  <LinearLayout id="module1" />
  <RelativeLayout id="module2" />
  <ListView id="module3" />
</LinearLayout>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  
public class DemoActivity extends Activity implements Module1Delegate, Module2Delegate {
  @Override
  public void onCreate(Bundle bundle) {
    setContentView(R.layout.demo_activity);
    Module1 module1 = new Module1(findViewById(R.id.module1), this);
    Module2 module2 = new Module2(findViewById(R.id.module2), this);
    Module3 module3 = new Module3(findViewById(R.id.module3));
    module1.render();
    module2.render();
  }

  @Override
  public void onModule1() {
    Log.e("Demo", "module1 say hello to the world.");
  }

  @Override
  public void onModule2(boolean show) {
    if (show) {
      module3.show();
    } else {
      module3.hide();
    }
  }

其实,还可以做的更彻底一些,那就是Activity中的布局都由ViewStub来组装,然后由各个子模块来决定如何布局。

对于多层全屏层叠的应用来说,要简单一些,对于每一层都可以由Activity或者Fragment来实现,如果业务层已经抽离出来,就都可以直接调用业务层来获取数据,因此也不会有传递数据的麻烦。

做好了分层和模块化,我相信,能解决绝大多数应用遇到的问题。至于模块内部用什么MVP,MVC,MVVM,其实真的无大害,因为模块内部的实现方式不影响其他模块,也不影响外部管理和level更高的类。

把基本的原则做到就够了

编程是一项社会活动,所以人和人与人之间的关系才是核心,优秀的人,你发现他也没有用什么MVP,什么MVC,什么高大上的设计模式和算法,但是他的代码是很清晰,很容易看懂。有些即使号称用什么高大上的,最先进的设计模式,但是代码仍是一坨坨的,可能连他自己都看不懂。

把基本的抽象和封装真正做到位了,就够了,代码水平可以的话,再能做到命名见名知义,小而活的方法,小而活的类,一个方法只做一件事,一个类只做一件事情。做到这些,也就够了。

至于什么高大上的MVP,什么XP,什么TDD,什么结对,其实都是浮云,如果你的水平比较高,代码sense较高,那么用不用这些方法差别不大。

MVP的核心目的是方便UT,因为把展示层的逻辑都集中在P,而P又不依赖于具体的View和Model,所以可以随便Mock一个View和一个Model来测试P,甚至P可以独立于平台的限制来单独的测试。所以,如果你不搞UT,以不以MVP方式来实现,其实没啥影响,甚至网上不少人还专门为MVP而弄出几个抽象的类,把Activity啥的封装了一下,号称MVP框架,毫无实用价值。软件方法,切忌生搬硬套,一定要先理解透彻方法,再理解透彻你的问题和环境限制,然后灵活运动,什么叫理解透彻呢?就是你能给别人讲明白时。这说起来还是太抽象,只能在实际运用中慢慢领悟。

再有就是Unit Testing这玩意儿,实际的意义也没有那么大,要知道写测试代码通常要比生产代码花更多的精力,前提还是你的代码写的可测,可测性比可读性还要难一点,说白了这对开发者水平的要求相当的高,不是看了一遍书,学习一下JUnit就能搞得好的。还有就是如果你的需求经常变动,移动互联时代这是家常便饭,那么做UT会让开发量double甚至tripple,因为之前写的UT全没有用。

还想说一点就是,软件开发方法这东西必须是由上向下推动,也就是由老板带头来推动,否则技术小组长或者开发者自己是很难推得动的。特别是像UT,Code Review或者结对之类的会“降低开发效率”的方法。这些方法短期内不会提升效率和质量,只会降低需求的产出率,平均开发水平比较高的团队也至少要几个月后才能真正的适应这些方法,然后才有可能提高效率和提高质量。如果不是老板主动推动,谁能受得了呢?KPI咋整?

结论

MVP或者MVVM带来最大的好处是:

  • 方便移植
  • 方便UT

另外,要注意MVP仅是展示层的方法论。应用整体还是要进行分层和模块化。如果分层和模块化进行的彻底,并且在移植和UT没有强烈的需求,其实MVP与不P真的不重要。

参考资源

  • Android中的MVP
  • MVP模式在Android开发中的应用
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

说说Android的MVP模式 的相关文章

随机推荐

  • 浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程

    简介 xff1a 本文试图完整地描述 Linux 系统中 C 语言编程中的时间问题 主要内容包括应用程序中的时间编程方法 xff1b 时钟硬件简介 xff1b Glibc 时间函数的实现以及 Linux 内核对时间的支持和实现原理 这是第
  • for i in enumerate(): 解析

    总而言之enumerate就是枚举的意思 xff0c 把元素一个个列举出来 xff0c 第一个是什么 xff0c 第二个是什么 xff0c 所以他返回的是元素以及对应的索引 line span class token operator 61
  • cv.threshold()浅谈

    threshold xff0c 是 阈值 临界值 门槛 的意思 其模糊一点的原理就是 xff1a 对数值与阈值进行比较 xff0c 针对比较结果 结果是指 xff1a 高 xff0c 还是低 xff0c 其中在本人的阅历中没有等于 xff0
  • 人工智能基础——什么是智能(智能的特征)

    智能的概念 xff1a 智能及智能的本质是古今中外许多哲学家 脑科学家一直在努力探索和研究的问题 xff0c 但至今任然没有完全了解 不过生成了以下几种学派 xff1a 1 思维理论 xff1a 认为智能是思维的核心 2 知识阈值理论 xf
  • 人工智能基础——知识的表示方法,一阶谓词逻辑表示法

    知识的表示 xff1a 就是将人类的知识形式化 xff08 符号 xff09 或模型化 xff08 结构 xff09 这样有利于 xff1a 对知识的组织维护与管理 便于对知识的增删改查 表示方法 xff1a 一阶谓词逻辑表示法 命题 xf
  • 最优化理论基础与方法学习笔记——凸集与凸函数以及手写定理证明

    文章目录 凸集的定义凸集的几何意义有关凸集的定理 定理1 4 2内点 边界点和闭包的定义定义1 4 3 超平面的定义定理1 4 3 投影定理定理1 4 4 点与凸集的分离定理定理1 4 5 支撑超平面定理定义1 4 4 凸函数的定义定义1
  • 人工智能基础——知识的表示方法,语义网络表示方法

    语义网络 以个体为中心的组织知识的语义联系实例联系泛化联系聚集联系属性联系 以谓词或关系为中心组织知识的语义联系以关系 xff08 谓词 xff09 为中心组织知识的语义联系连接词在语义网络中的表示方法合取析取否定蕴含 变元和量词在语义网络
  • Ubuntu学习笔记——隐藏文件

    在Linux中 xff0c 隐藏文件可以用 ls a来查看 隐藏文件是 filename 的形式的
  • 概率论与数理统计学习笔记——参数估计

    参数估计 xff1a 估计参数 方法 xff1a 据估计法 极大似然法 矩估计法 xff1a 样本矩等于总体矩 xff08 当样本容量很大时 xff09 xff0c 总体矩就是我们所说的期望 xff0c 比如k阶总体矩就是X的k次方 xff
  • CMakeList.txt/Clion中添加头文件和库

    cmake minimum required VERSION 3 6 project capi lua include directories usr include find library LUALIB lua usr lib set
  • 人工智能基础——谓词公式化为子句集的方法

    谓词公式化为子句集 基本定义谓词公式化为子句集的步骤定理3 1 基本定义 谓词公式化为子句集的步骤 定理3 1 基本定义 原子谓词公式 xff1a 一个不能够再分解的命题 原子谓词公式及其否定统称为文字 xff0c P称为正文字 P称为负文
  • Ubuntu学习笔记——磁盘以及磁盘分区和文件系统

    磁盘 磁盘分区和文件系统 磁盘格式化磁盘设备命名使用命令行工具管理磁盘分区和文件系统 磁盘格式化 磁盘设备命名 使用命令行工具管理磁盘分区和文件系统 磁盘格式化 xff1a 低级格式化 xff1a 空白磁盘划分柱面 分区以及磁道 高级格式化
  • 几何分布的期望推导

    推导过程
  • CRC循环冗余校验纠错中循环左移的原因以及一些新感受

    CRC循环冗余校验纠错中循环左移的原因以及一些新感受 问题的描述理解已知前提左移的需求背景 xff1a 为什么需要左移 新知 问题的描述 理解 已知前提 1 我们讨论的是只有一位代码出错的情况 xff0c 多位出错的情况由于 纠错代价过大而
  • 把在forward函数里面添加的层放到GPU中

    把在forward函数里面添加的层放到GPU中 问题描述问题解决 问题描述 借鉴GoogleNet和NiN的思想 xff0c 我想在Inception最后在添加两个卷积全连接层 xff0c 但是这样子写会报错 xff0c 说是final这层
  • 自己创建XXConfig.cmake文件

    创建自定义的XXConfig cmake文件 xff1a 我们在使用cmake进行代码构建的时候 xff0c 当需要通过find package XXX 来配置相关库的路径 xff08 头文件路径 链接库路径 xff09 的时候 xff0c
  • ucosII的书籍

  • ROS中在一个功能包中导入另一个功能包的python模块

    目录 1 引言 2 创建一个功能包 3 安装功能包到ROS环境 3 1 编辑CMakeLists txt 3 2 编辑setup py 3 3 安装到ROS环境 4 模块导入 4 1 创建验证功能包 4 2 编译工作空间 4 3 运行ros
  • Android技巧:学习使用GridLayout

    GridLayout是一个非常强大的网格类布局 xff0c 它不但能像TableLayout那样 xff0c 实现网格类布局 xff0c 但它更为强大的地方在于每个Cell的大小可以横向或者纵向拉伸 xff0c 每个Cell的对齐方式也有很
  • 说说Android的MVP模式

    更多精彩博客 安卓应用开发是一个看似容易 xff0c 实则很难的一门苦活儿 上手容易 xff0c 看几天Java xff0c 看看四大组件咋用 xff0c 就能整出个不太难看的页面来 但是想要做好 xff0c 却是很难 系统框架和系统组件封