原文链接Artech博客文章:
原文如下:
之前写了一篇名称为《》的文章,主要表达本人对于MVP模式下(主要针对Passive View变体)View和Presenter之间的关系,以及它们之间的交互应该采用怎样的原则和方式的看法。园子里的朋友对此展开了一些讨论,尤其是是一个叫做的朋友转述了另一篇文章提出的关于CAB中关于MVP模式的14条规则,和本人的观点有很多相似之处,当然也有一些不一致的地方。为此,在本篇文章中,就此进行一些必要的补充。
一、CAB(Componsable Application Block)基于14条MVP规则
为了让所有的人都能够阅读上面提及的那篇文章,我将其转载我个人的博客中,有兴趣的读者可以仔细阅读(《》)。文章作者提出的观点和我在《》中的观点是一致的,即Presenter对于View应该是相对透明的,View不能直接对Presenter进行操作,目的是实现Presenter和View之间的分离(The generated code is not quite as I would like it, I prefer that the view has no knowledge of the presenter and no direct access to it either as this gives a cleaner separation between views and presenters)。文章作者倾向采用事件注册的方式实现Presenter和View,不过我不太清楚具体是注册View还是Presenter的时间。在《》中对MVP的实现手段,我采用的是在Presenter注册View的事件。关于View和Presenter的分离,我的做法作得更加彻底一点——根本就不给开发者从View调用Presenter的机会。
文中提出了关于CAB的14条编写符合MVP规范的规则,在这里我特将其翻译成中文:
1、所有的View(包括View的接口)的名称应该以View作为后缀,比如TaskView/ITaskView;
2、所有的Presenter名称应该以Presenter作为后缀,比如TaskViewPresenter;
3、Presenter完成Use Case处理逻辑,对GUI控件的处理应该在View中实现;
4、View调用Presenter的方法应该像触发事件异常,通过调用OnXxx方法的方式来实现;
5、应该尽可能地限制View对Presenter的调用,并且调用的方式限于按照“事件”的形式,比如_presenter.OnViewReady();
6、View不允许通过Presenter直接调用Model和Service,并且Presenter的方法应该是不具有返回值的;
7、Presenter必须通过View接口的方式调用View
8、除了对View接口成员的实现外,View中的其他方法不应该是public的;
9、除了CAB ModuleController 对View的加载和限制外,View只能被Presenter调用;
10、View接口方法应该基于Use Case的逻辑起一个有意义的名称,比如SetDataSource这样的方法名称是不合法的;
11、View接口的成员应该仅限于方法,不应该包含属性;
12、所有的数据应用保持在Model中
13、定义在View接口的方法不应该包含对GUI空间名称的引用(比如AddExplorerBarGroup),因为这会使Presenter知道View太多关于实现方面的细节;
14、尽量让View的方法名称反映Use Case的业务逻辑,这样可以使你的代码具有自表述性并更加易于理解。
再次回到《》中讨论的话题,在我看来,抛开1和2对View的Presenter命名的规范外,其余的12条规则体现了MVP关于View和Presenter之间应该具有的关系,以及我们应该采取的正确的Presenter和View交互方式。View和Presenter之间的关系,可以通过对Presenter的角色界定来体现,在整个MVP体系中Presenter扮演的是协调者的角色。
二、Presenter是协调者,是整个MVP体系的控制中心
如果我们将MVP体系比喻成一个社团(考虑到中国没有黑社会,这里我们说社团),我们经常看见的往往是那些顶着黄毛,纹着纹身的街头小混混,你可以将它们看成是View。也就是说View是和外界打交道的人,是行动者,就像是到处砍人、收保护费,以及和别的社团抢地盘的都是这些处于社团基层的小混混一样。View永远处于处于幕前,和最终用户进行交互,但是地位却不高。对于用户的UI交互请求该如何进行处理,View做不了主,它需要向大佬汇报。所以View永远不可能是决策者,仅仅是一个汇报者而已。
Presenter才是真正的大佬、话事人,执龙头杖的。Presenter生藏不露,最终用户感知不但它的存在,就像社团大哥大都隐藏的比较好,甚至以政府官员(比如文强大哥)或者是电影公司老板(比如香港的XXX电影公司)的身份出现。但是,我们知道,他才是整个社团的主导、核心,是整个事务的决策者和执行者,使能够调动相关资源的协调者,而这个事务,你可以理解为Use Case。也就是说,Presenter是对Use Case的反映,UI交互逻辑的处理流程定义在Presenter中,但是具体的实现并不是完全在Presenter中,这一点很重要,下面一节中我们还会谈到。
我们还是把话题回到交互上面。这里的交互,即View和Presenter之间如果沟通,是比较特别的。谈到沟通,很多人都会认为这是一个双向的问题,而View和Presenter采用单向的沟通方式,这和某些上下级的沟通方式有点类似——下级单方地向上级汇报工作,上级单方的向下级下达命名。这在等级观念深重社团中更是如此,我们习惯的场景是这样的:小混混向大佬说:“我们的场子昨天晚上被砸了,怀疑是XXX干的”。大佬说:“恩,知道了,下去吧!”。真正有前途的小混混不会说“我们的场子昨天晚上被砸了,怀疑是XXX干的,我们什么时候去砍他?”。真正有范儿的大佬不会马上命令你在什么时候、什么地点、带多少兄弟去砍人,而在计划实施的时候会向相关成员下达砍人的指令。
反映在真正View|Presenter的交互上面,就是说:View单纯地将用户的交互请求汇报给Presenter;Presenter接收到请求之后,整合相应的资源、执行相应的处理逻辑。对处理流程的某一个步骤,如果设置到业务逻辑和数据模型,则调用Model,如果涉及到对GUI控件的操作,还会调用View。View将交互请求递交给Presenter之后,不需要考虑后续需要做什么,因为Presenter会在适当的时候命令View该如何做。
所以说,Presenter是整个体系的驱动着,View和Presenter不应该是一种拉的关系,而是一种推的关系。View将用户交互请求推给Presenter,Presenter将数据推给View并驱动View完成相应的UI相应。 正因为如此,上面的MVP规则列表中才规定Presenter的方法不需要返回值,View的接口不需要定义属性。实际在我个人看来,Presenter和View接口都应该只包含返回类型为void的方法即可。
三、Presenter不关注具体的实现细节
谈到这里有人会说,所有的关于UI处理逻辑定义在Presenter中,那么会不会使Presenter变得臃肿不堪呢?持这种观点的人实际上走入了另外一个误区。我曾经看到过有人写过这样一个极端的例子——将View的所有控件都以属性的方式公布出来,定义在View接口中,所有控件相关的操作都实现在Presenter中。很明显这是不对的,虽然这个例子很极端,但是我想很多对MVP不是太了解的人或多或少会犯这种错误。
在上面一节中,我们说过UI交互逻辑的处理流程定义在Presenter中,但是具体的实现并不是完全在Presenter中。Presenter是蓝图的设计者,并不关注实现的细节。大佬只是制定行动计划,真正砍人、收保护费和抢地盘这种操作性强的工种的还是属于小混混们。
所以该View干的事一件也逃不了,只是View不考虑什么时候干,因为Presenter会在适当的时候通知你,View得保证随叫随到。为了保证Presenter能够有效地控制View,需要将这些操作定义在接口中。既然定义在接口中,操作的粒度就不能太细。Presenter关于的是整个Use Case的处理流程,所以定义在View接口中的操作也应该采用处理流程相关的语言来定义。