也谈MVP

作者: 欧阳锋 | 来源:发表于2016-07-28 21:03 被阅读557次

    Most Valuable Player ?

    lol,你以为你猜对了吗?其实这里的MVP指的是一种软件设计模式,MODEL-VIEW-PRESENTER 是一种分层设计的思想。可是为什么是谈MVP呢?因为MVP已经火遍了大江南北,网络上相关的文章已经不计其数。而我只想提出自己的一些见解和思考。

    悄悄火起来的MVP

    在MVP出现之前,比较经典的分层架构就是MVC。MVC已经被使用了很多年,并且被证明是一个非常好的分层设计模型。可为什么又会出现奇葩的MVP呢?这是因为在程序越来越大的时候,部分程序员发现代码维护变得困难起来。为什么会这样,看图:

    MVC

    可以看到View可以直接通过Model层获取数据,也可以间接通过Controller获取数据,这就造成了View层既有直接访问Model层的代码,也有间接通过Controller访问Model的代码,在代码量较大的情况下就造成了维护困难的窘境。MVP正是意识到了这个问题,才出现了使用Presenter层间接访问Model的解决方案,继续看图:

    MVP

    简单来说,MVP的三层架构基本如上图。不过,你从网上看到的图可能和上图不一样。网上的图其中还会有一根线从Presenter层指向View。这并不代表Presenter要直接去访问View。这只是说Presenter需要和View之间保持同步。View的变化要反映到Presenter中,而Presenter的变化也反映到View中,二者之间通常使用回调的方式保持同步。

    在Android应用架构中,MVC和MVP对于View的观点也不一致。MVC三层架构将Activity/Fragment视为Controller层,View层为布局相关的View控件。而MVP三层架构中则将Activity/Fragment视为View层,Presenter层独立的抽象层。

    关于MVP将Activity/Fragment视为View层一直存在很大的争议,了解更多关于MVP争议的文章,请关注文章最后的参考文档。

    那么,为什么说MVP的设计就解决了代码混乱、不易于维护的问题了呢?
    从图中可以看到,MVP三层架构中,View层所有的数据访问都必须经过Presenter,这样所有关于数据访问的代码都在Presenter中呈现,从而极大地解决了维护成本问题。

    MVP设计的另外一个比较大的优点,就是:它解决了Android应用一直以来单元测试困难的问题。通过MVP模型,我们可以直接对Presenter层写单元测试,Presenter层是纯Java代码,不需要牵扯到Android层,可以非常方便地进行单元测试,减少Bug的发生。

    不过话说回来,中国程序员是很少写单元测试的, lol。

    那么,MVP的设计是否就没有缺点了呢?答案是:当然不是!

    MVP的演进

    虽然MVP极大地解决了:

    • 代码维护困难
    • 单元测试困难

    的问题。
    但在大型应用项目中,仍然有可能造成Presenter层代码量巨大,而难以维护,有什么好的办法解决这个问题呢?
    于是,MVP Clean模型诞生了!
    MVP Clean模型其实还是MVP模型,只是将Presenter的数据操作再进行了一定的抽象,增加了Domain(以下简称为Interactor)层,Interactor层专门用于与Model进行交互,Presenter不再直接与Model进行交互。这真的可以解决Presenter层代码臃肿的问题吗?答案是:可以!
    有人肯定已经有了一个疑问,这不过是挂羊头卖狗肉而已,把Presenter层的代码迁移到了Interactor。其实并不是这样,Presenter访问Model的代码可能非常大,这样可以将Interactor拆分为多个,即一个Presenter对应多个Interactor进行不同功能的数据访问,单元测试就转化到对单个Interactor进行测试即可。由此可见,这的确解决了Presenter层代码臃肿的问题。

    MVP Clean模型的基础上,是否还有优化的空间呢?答案是:有。

    在上文的MVP演进中,仅仅是对Presenter层进行了优化。对于Model层,其实还有进步的空间。对于Model层比较经典的做法就是使用Repository模式进行数据的封装。对于Domain层只暴露对应的接口,Domain层并不知道Model是什么数据,是来自网络,还是数据库,还是缓存... 即保证了实现可以随时替换的可能。

    这里在设计Model层的时候,一定要切记勃兰特·梅耶的OCP(开闭)原则。即:要保证充分的可扩展性,但要避免直接修改源码。

    到这里为止,对MVP的封装基本可以告一段落。可是,还有一个比较棘手的问题。Presenter和View层的同步问题,如何解决?
    Activity/Fragment都有自己的生命周期,Presenter也必须保证其生命周期与Activity/Fragment同步。关于这个问题,网络上有几种解决方案。其中,最容易想到的就是利用Activity/Fragment本身的生命周期状态保存恢复方法。这种方式的确可以解决这个问题,却带来了额外的维护成本。于是,使用Android提供的Loader类进行Presenter生命周期维护的方法就应运而生了。为什么可以做到生命周期的维护,这里就不赘述了,请大家点击文章最下方的参考文档列表进行查看。

    MVP整体设计

    通过上面的讨论,我们对MVP的整体设计如下:
    1) 数据流向: UI -> Presenter -> Interactor -> Repository -> Data
    2)Presenter对象创建:使用Loader创建

    基于这样一个结构,我们来看一下完成登录功能可能需要的类:

    登录

    天哪!仅仅完成一个登录功能,居然需要至少7个类。而且,还不包括框架类。

    直到这里,我想MVP的缺点也暴露无遗了!
    是否有办法减少过多的类呢?答案是:有!
    我们来看一下谷歌官方的解决方案:
    iosched : 这是谷歌IO大会官方APP

    iosched

    对这个工程简单分析就知道,谷歌使用了一个比较巧妙的方法维护Presenter的生命周期,即使用一个冗余的Fragment作为Presenter的实现类。同时,Presenter类仅仅是设置了一些属性而已。正在的数据查询操作放在了Model类中去处理。
    对于View接口,它也使用了一个统一的UpdatableView进行处理。

    总结一下:谷歌使用了Fragment作为Presenter实现,所有的UI类都依赖这同一个Presenter实现,对于View接口,也使用了统一的封装UpdatableView。这样,在某个功能模块里面,其实只需要实现相应的Model即可,极大地减少过多类的问题。

    咋一看,禁不住要为谷歌如此碉堡了的设计鼓掌。可仔细一想,谷歌的这种方式,并不适用于所有情况。谷歌的这个app仅仅设计到数据的存取,逻辑相对简单。对于一个复杂功能的app,很难对Presenter和View进行统一封装。所以,这也是目前MVP设计的窘境。即:你可以这样设计,但你却很难确保你的团队都能理解你的设计,都能追随你的设计,这恐怕要一个标准的规范文档才可以做到比较统一。

    关于MVP设计的思考

    MVP设计模式解决了极大一部分困扰程序员的问题,但也带来了过度封装的嫌疑。对于不同规模的app,一定要学会适当取舍;同时,也要学会对MVP进行二次封装,减少开发成本。对于MVP存在的问题,谷歌也提出了自己的解决方案,即:Data Binding。Data Binding是一种模板设计模式,被广泛用于Web开发中。谷歌的这种做法,也让我们相信天下大势,必趋一统。Data Binding解决了一部分MVP问题,也带来了一些新的问题。由此看来,我们离规范的Android应用架构设计依然很遥远,让我们一起期待这一天早日到来。

    说出你的想法

    如果你对MVP设计模式有自己独到的见解的话,请在文章下方写下你的评论告诉我,我期待着与你一起探讨,/WX。

    参考文档

    The Clean Architecture
    MVP for Android: how to organize the presentation layer
    Introduction to Model View Presenter on Android
    An Introduction to Model View Presenter on Android
    Android MVP 详解(上)
    Android MVP 详解(下)
    iosched
    android-architecture

    注:如果你喜欢我的文章,别忘了点击这里 打开页面,点击屏幕左侧的添加关注按钮关注我哦。如果你觉得这篇文章对你有帮助,也希望赏点酒钱哦!

    相关文章

      网友评论

          本文标题:也谈MVP

          本文链接:https://www.haomeiwen.com/subject/zqwajttx.html