我所理解的MVP模式

前几天看了一些关于MVP的一些文章,发展每个人都有自己的见解,并不能达到高度的统一,不过笔者还是大受裨益。今天笔者也谈谈自己对MVP的一些理解,供大家学习交流。

由于笔者是一枚iOS开发,所以接下来有些地方会结合iOS来,以期将笔者想说的都讲的清楚明白。

Demo地址:https://github.com/yuexygoodman/MVPExample

MVP与其说是设计模式,还不如说是一种程序架构范式,说明了我们应该怎样更好的组织整个项目的代码和资源。

MVP模块关系图

M:Model层,数据服务层,负责数据的增删改查。在服务端就包括mysql数据库操作、本地cache等;在客户端就包括调用服务器API、各种形式的数据缓存等。有些朋友将Model层理解为数据模型层,数据模型归根结底是数据,设计数据模型是为了在面向对象的程序设计中更好的表达数据。所以数据模型贯穿于整个应用程序,不应该将Model层单单理解为是数据模型。但Model层往往负责将接收到的数据转化为相应的数据模型供上层使用。

V:View层,视图界面层,负责UI的渲染、子视图的组织、UI事件、用户交互等。在有些网友看来,View层是比较轻的一层,大多数时候只需要调用系统的UI控件,绑定需要的UI事件就完事了,很多平台甚至拖拖控件就行。但是实际上是因为平台为我们做了大多数的事,我们不需要去考虑怎么有效的渲染界面,也不需要去考虑怎样去实现各种各样的交互事件(触摸),只需要关注应用本身就可以了。

P:Presenter层,有得朋友将其叫作发布者,百度翻译为主持人,笔者觉得后者更贴切些。 Presenter既是中间人,在ViewModel之间起到桥梁的作用,又是一个独立的大模块,封装了业务的复杂度,将UI和业务逻辑拆分开来,使UI和业务都可以独立的进行变化。从整体的数据流向上看,PresenterModel层获取数据,并通过接口发送给View层展示;View层将用户交互传递给Presenter,由Presenter完成相应的业务逻辑,这其中可能会有Model层的参与,比如Presenter调用Model层的接口来保存数据。

面向接口编程的MVP

在理想状态下,ModelViewPresenter这三层都应该是面向接口(interface/protocol)编程的,从而达到低耦合,高复用的效果。同时,由于业务不依赖于UI,使单元测试也更容易进行。很多人并没有单元测试的重要性,一个网友描述的挺好的,这里直接将原话贴出来:

单元测试可以大大地减少程序运行时才能发现的问题,这通常可以节省「用户反馈」->「Bug修复」->「新版本发布」->「用户安装新版本」这个耗时长达一周以上的过程。所以,程序的可测试性对于程序的稳定性是异常重要的。

上面都说了,完全面向接口编程是最理想的状态。实际情况则是过份的设计大大增加了开发过程中的复杂度,降低了开发的效率,最终可能影响项目的进度。所以在实际的项目开发中,对MVP的应用深度应该视情况而定:

  • 当某一个View需要在项目中的多处复用而View本身相对来说比较复杂时,P层应该面向接口编程,而View只是持有特定接口类型的引用。这里推荐采用代理模式,即在View层定义一个特定的Protocol,让P层所有需要与该View层对接的类都去实现这个Protocol以便接收View层发送过来的事件,这样同一个View层可以对接多个不同的P层,View可以方便的进行复用。如:UITableView等。
  • 当我们需要针对同一个业务经常更换用户界面时,为了让每一次改动都不会对P层造成大的影响,在设计View层时则需要完全面向接口进行设计,P层持有IView接口的引用,这样不管View层的具体类怎么变动,View层和P层的交互始终依赖的是一直未变动的程序接口IViewP层则不需要做大的改动。
  • 如果你没有诸如切换数据存储方式(mysql->oracle, file->sqlite)等需求时,往往不需要用面向接口的方式来设计Model层。但有一点还是应该留意:iOS开发中,大多数情况下,我们在Model层都有两件事去做,一是调用网络接口访问服务器,再是本地缓存。数据既可以来自于网络也可以来自于缓存,但这些细节对P层来说应该是透明的,我们不能将获取数据的这两种方式都暴露给P层,而是需要以此为基础设计统一的接口。这样做的好处是,一方面简化了P层对Model层的调用,API操作数据具有一致性;另一方面,更改缓存机制,调整网络接口都不会对P层产生影响。

笔者认为,实现MVP模式的最低原则是:

  1. ViewModel之间不能直接进行交互,必须通过Presenter来交流数据;
  2. 尽量的将业务逻辑和UI展示分开;
  3. 尽量使用面向接口的方式来实现MVP三层,特别是对于ViewPresenter之间的交互。
iOS平台上的MVP

在iOS平台上,目前大多数采用的MVC的实现,实际上已经符合了MVP的特点了,只是ViewController做了更多的事。ViewController充当了Presenter的角色,在ViewModel之间起到了桥梁的作用,同时封装了业务逻辑。除此之外,ViewController还负责界面的生命周期、视图组织、页面跳转等。ViewController虽然属于View层,但很多时候我们还是会用它来封装业务逻辑,由于承担了过多的职责,在有些复杂的情况下,ViewController往往变得不堪重负,给维护代码和新增功能都带来了不利影响。为了给ViewController瘦身,我们可以采取以下措施,供参考:

  • View的展示逻辑放到相应的View中。典型的就是UITableView的delegate和datasource,我们可以自定义UITableView的子类,将相应的代理方式都移植到子类中去,然后暴露一个接收数据的方法供ViewController调用,这样来给ViewController瘦身。
  • 将复杂的View-View交互剥离并封装起来。比如界面上的UIScrollView滚动时,我们需要不断的调整界面头部的透明度。这些View之间的交互本身并不涉及具体的业务逻辑,我们就可以将其封装起来,达到使ViewController瘦身的目的。另外,如果View-View的交互具有一般性的话,我们还可以在其他需要的地方方便的复用这种交互行为,一举两得。
  • 统一处理界面跳转。界面跳转放到ViewController中再合适不过了,ViewController天生就具备这样的能力。我们要做的,就是尽量将跳转统一封装到基类中,而不要同样的跳转逻辑在每个具体的VC中都去写一遍。
  • 如果一个ViewController包含多个复杂的业务逻辑,我们应该为这些定义多个Presenter来分别进行处理。ViewController本身只需要创建ViewPresenter,并将具体的View将具体的Presenter对应起来就行了。这样,ViewController就变得孑然一身了。

最后给出一个iOS平台上实现MVP模式的例子,供大家参考:
由于代码比较多,这里只贴出主要的接口代码,项目源码请查看https://github.com/yuexygoodman/MVPExample
1、登录界面,View层接口

@protocol ILoginView<NSObject>

- (void)setPresenter:(id<ILoginPresenter>)presenter;

- (void)loadWithLastAccount:(NSString *)account pwd:(NSString *)pwd;//显示历史账号

- (void)showLoginError:(NSString *)err;//显示登陆错误

- (void)showLoading;//显示正在登陆

- (void)hideLoading;//登陆完成后隐藏

- (void)navToMain;//切换界面到主界面

@end

2、登录界面 Presenter接口

@protocol ILoginPresenter<NSObject>

- (void)setLoginView:(id<ILoginView>)loginView;

- (void)start;

- (void)onLoginWithAccount:(NSString *)account pwd:(NSString *)pwd;//处理登录逻辑

@end

3、朋友管理界面 View层接口

@protocol IFriendView<NSObject>

- (void)setPresenter:(id<IFriendPresenter>)presenter;

- (void)loadWithFriends:(NSArray<Friend *> *)friends;//显示列表

- (void)confirmFriend:(Friend *)fri msg:(NSString *)msg;//向用户展示再次确认窗口

- (void)unShowingForFriend:(Friend *)fri;//取消被删除朋友的显示

- (void)showRemoveError:(NSString *)msg;//显示错误

4、朋友管理界面 Presenter接口

@protocol IFriendPresenter<NSObject>

- (void)setFriendView:(id<IFriendView>)friendView;

- (void)start;//启动

- (void)onRemoveFriend:(Friend *)fri;//删除业务

- (void)onSureRemoveFriend:(Friend *)fri;//直接删除

@end

再次给出Demo地址:https://github.com/yuexygoodman/MVPExample 欢迎大家访问
对于MVP模式的理解就到此为止了,下一篇笔者准备继续说一说MVVM,以上内容,均属笔者拙见。有什么不对的地方,烦请指教。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容