应用MVP 模式对遗留代码进行重构

一 AV(Autonomous View)自治视图

  • 在面向终端用户的应用中,都需要一个可视化的UI来与用户交互.这个UI称为View 视图.
  • 在早期,我们习惯将所有前台的逻辑,与视图揉在一起,称为AV 自治视图.
    • 这些逻辑包括:数据呈现(Display),用户动作的扑捉与响应,数据存储等.
  • .Net 的Winform 和ASP.NET Web Form 采用的都是事件驱动模型.
  • AV是将所有UI相关的逻辑都注册到视图本身,或者视图元素对应的事件上.
  • 人机交互应用的3个关注点.
    • 数据在UI上的展示.
    • UI处理逻辑.
    • 业务逻辑.
  • AV的缺陷
    • 首先,业务逻辑与UI无关,所以应该最大程度地被重用.而在AV中,业务逻辑糅合在UI中,无法重用.例如,从winform迁移到web form上.
    • 稳定性:业务逻辑>UI处理逻辑>UI.
      • "短板效应":�三者糅合在一起后,具有最弱稳定性的UI决定了整体的稳定性.
    • 任何涉及到UI的组件都是不可测试性的(至少是很难测试).所以AV对测试不友好.

�二. MVC模式

  • 针对AV的缺陷,采用SOC(关注点分离)来剥离3个部分.

  • 将人机交互应用分为3个部分:

    • Model:对应用状态和业务功能的封装.
      • 维护着整个应用的状态(数据和行为),并实现了所有的业务逻辑,可以看做为一个领域模型.
    • View:实现可视化界面的呈现,捕捉最终用户的交互操作(键盘,鼠标).
    • Controller.
      • View捕获到用户交互操作后会直接转发给Controller,后者完成相应的UI逻辑。
      • 如果需要涉及业务功能的调用,Controller会直接调用Model。
      • 在完成UI处理之后,Controller会根据需要控制原View或者创建新的View对用户交互操作予以响应.
  • View和Model存在直接的联系.View可以直接调用Model查询其状态信息。

    • 当Model状态发生改变的时候,它也可以直接通知View.
  • Model对View的数据状态改变通知,View对Controller的用户交互通知.都是单向的消息交换.

    • 可以使用事件机制来实现这两种通知.
    • 也可以通过观察者模式通过注册/订阅的方式来实现.
      • View 作为Model 的观察者,通过注册相应的事件来检测数据状态的改变.
      • Controller 作为View 的观察者,通过注册相应的事件来处理用户的交互操作.

三. MVP模式

  • MVC模式存在的问题

    • View和Model可以绕过Controller来直接进行交互.
    • 对于用户驱动的程序(人机交互),我们不需要Model来主动通知View数据状态的变化.所以,Model应该是完全独立的.


  • MVP模式的目标

    1. 测试(Unit Test)友好.
    • 关注点分离.
    • 正交性.
      • 每一个操作都只改变一件事情,而没有其它的副作用.
  • 解依赖

    • 对View 和Model 解耦.
    • 降低了Presenter 对View 的依赖.从依赖于具体的View 到依赖于抽象的IView 接口.
  • 交互

    • Presenter对Model的单向调用.
    • Presenter和View之间的双向交互.这个是核心.
  • Presenter 和View 之间交互的方式

    • PV(Passive View)
      • 为了不做对UI的测试(难到几乎不能),应该在UI中不进行UI逻辑的处理.

      • 一个被动的View.View中的UI元素(控件)不是由View本身操作,而是由Presenter控制对UI元素的操作.

      • 需要将View中的元素以属性或者其他方式暴露,以供Presenter操作.

      • 在数据绑定中,控件类型的选择应该是View内部的逻辑,不应该出现在Presenter中.

        • 所以,在IView的定义中,不能涉及到具体的控件类型.
        • 而是返回一种数据绑定所需的数据类型.
        • 然后在View内部处理数据到控件的绑定.
      • PV对测试友好,因为所有的UI处理逻辑都在Presenter中,便于测试.

      • 缺陷

        • 对于一个复杂的UI(含有很多元素),IView接口将会十分庞大.
        • Presenter需要对UI元素进行操作,所以要了解很多的UI细节.造成简单事情复杂化.
      • Soc

        • 将诸如格式化,数据绑定这些简单的UI逻辑移到View中.在View中进行一些简单的UI逻辑处理.
        • View本身仅实现单纯独立的UI逻辑,它处理的数据应该是Presenter推送给它的.
          • 所以View尽可能不维护数据状态.在Iview接口的定义中不包含属性.
        • Presenter所需的View状态应该是View在请求交互处理时给它的.

四. 第一次改造:最薄的View.

  • 起源:由于View持有对Presenter的引用,所以理论上,View是可以无限制地调用Presenter的.

    • 基于以前AV的编码习惯,很可能造成以下的问题:
      • 大部分(甚至所有)的UI处理逻辑都写到View中.
      • 而Presenter的作用就是Proxy,仅仅是调用View中的方法而已.
  • 采用事件订阅的方式来完成Presenter和View的交互.

    • 首先,在IView中定义事件Handler.
    • 为了隔离事件参数中e的类型污染(一些控件的事件参数,会引入一些测试不友好的类型),定义一系列的事件参数类型.
    • 然后,在View的控件事件处理函数中.
      • 将处理事件需要的上下文信息,包装到一个自定义的事件参数中,然后 Raise Event.
    • 最后,在Presenter中,订阅IView暴露的各种事件,并进行处理.处理时需要的上下文在自定义的事件参数中.
  • 优缺点

    • View只完成了纯粹的布局展示.
    • 在事件处理流程中,如果需要Cancel处理,会比较难做到.

五. 第二次的改造

  • 在View中调用Presenter的方法.完成部分的UI逻辑.

  • 工程划分(使用Company来替代真实信息).

    • Company.MVP.ICommonView.
      • 包含了对使用到的控件的抽象View接口,在每个接口中暴露出来Presenter需要使用到的属性和函数.
      • 每一种控件类型一个接口.
    • Company.MVP.ComonViews.
      • 对于每一个控件,实现一个继承了IXXXView接口的类.
      • 在这些类中,体现了具体控件的属性和方法的细节.
    • Company.MVP.Common.
      • 该工程含有3个子文件夹.
        • ModelObjects. Model的一部分,业务模型的抽象类.
        • Service. Model的另外一部分,定义了数据访问接口.
        • View:定义了UI页面需要实现的接口.
    • Company.MVP.Presenter.
      • Presenter的具体实现.
    • Company.MVP.Service.
      • 数据访问接口的具体实现.
    • Company.Client.
      • 具体的UI工程.会实现Common中View的UI页面接口.
  • 工程间依赖.
    • Prensenter仅仅依赖于ICommonView和Common.而跟具体的UI控件类型,具体的UI画面无关.
    • 所以,可以使用一个Presenter来对应多个的View展示(Client).
  • 单元测试

    • 针对Presenter.
      • 对于Service和View,由于P中操作的是两者的接口.所以可以使用Mock来模拟这两个部分.
      • 而Model是可以简单地New出来的,不需要进行Mock.
    • 针对Model.
      • 使用业务场景,进行测试.而且对其测试时,不需要进行Mock.
    • 针对View.
      • 可以进行少量的测试.因为有IView接口,所以可以Mock控件的属性和行为,来针对UI页面进行测试.
  • 更换控件类型

    • UI应用中,最经常遇到的情形.例如,现在要将界面上的一个TextBox控件替换为EditText控件
      • 在UI实现的Client工程的具体页面类上,将实例化以前的成员时使用的类型从TextBoxView修改为EditTextView即可.
      • 其他的类和工程不需要修改.
      • 改动被限定在了特定的地方.避免了短板效应.

六. 总结

  1. 关于代码量
  • 使用MVP模式后,代码量是肯定不会比原先的少的.

  • 考虑到View的重用,以及子Presenter的重用.代码量增加的也不多.

  • 关于控件的View类型的接口抽象及实现.

    • 对于控件的View的接口,可以只针对一个页面,也可以在工程前期,定义好对一个控件所需的所有的操作.这样就在全系统中使用一份View的接口.
    • View接口对外暴露的应该是操作,而不是以控件属性/方法的视角看待.也就是说Prensenter需要对控件进行什么类型的操作,就暴露一个这样的操作出来.
  • 关于控件差异性的问题.

    • 系统中不同界面中,同一控件的操作接口可能是不同的.
    • 按照MVP的本意,是没有View重用的概念的.
    • 但是,我们可以将同一控件基本的公用行为抽象为一个接口,然后使用一个类来实现它.然后在有特殊操作接口的画面中,再定义一个继承自公用接口的接口,然后使用一个类继承公用类,并实现该接口.
  • 关于控件的事件链.

    • 在现有的代码中,有很多地方用到了事件链的连锁效应.
    • 个人认为,这是一种不太好的编程方式.这样控件之间相互的依赖关系变得如此的复杂.改动事件链上的任何一个控件的任何一个事件处理,都需要查看其连带的连锁反映.
    • 在MVP中,我们在处理一个控件的操作时,会把所有控件需要展示的内容一次性地处理好,然后一把交给View进行展示.而不是使用事件的连锁效应.
    • 这样,就解除了控件之间在事件上的相互依赖关系.
  • 关于单元测试.

    • 对于业务系统的单元测试,纯粹的代码覆盖率是没有意义的.
    • 需要关注的是测试的场景覆盖率.
    • 即使覆盖百分百的代码.但是漏测了一种Case,一样会出现Bug.
    • 所以,我们需要有很清晰的业务逻辑说明,来指导我们进行单元测试时的Case场景输入.
  • 事件处理流程三部曲

    • IView中定义Event.
      Event ButtonClick.
    • View中触发事件.
Private withevents  _item as button
  Public sub itemClick() handles _item.Click
RaiseEvent  ButtonClick
  • Presenter中挂接并处理事件
    AddHandler OKButton.ButtonClick , Addressof Save.

  • 目标

    • 一个(种)控件,对外提供统一的行为接口.
      • 行为包括:属性,方法,事件.
  • 画面类职责清晰.

    • 仅包含了控件的集合.
    • 没有任何的逻辑处理代码.
  • 更换控件类型时,改动最小.

    • 仅需更改画面类中New控时使用的实际View类型.
  • 业务代码和控件逻辑的分离.

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

推荐阅读更多精彩内容