应用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.
        • 而3者糅合在一起后,具有最弱稳定性的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模式的目标
      • 测试(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即可.
        • 其他的类和工程不需要修改.
        • 改动被限定在了特定的地方.避免了短板效应.
  • 总结
    • 关于代码量

      • 使用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是完全独立的,不依赖于任何模块.

应用MVP模式对遗留代码进行重构,布布扣,bubuko.com

时间: 2024-10-08 17:35:15

应用MVP模式对遗留代码进行重构的相关文章

Android MVP模式学习(二)----Data Binding结合

公司项目最近已经用MVP模式重新构造完成,重构后的项目直观明了,代码复用性高,易于调试维护. 之后主管让我去了解MVC,MVP ,MVVM,Data Binding相关的知识,于是便有了下面这篇博客.参考资料: 选择恐惧症的福音!教你认清MVC,MVP和MVVM 完全掌握Android Data Binding 这里很感谢这两篇文章的理论与技术支持,感谢这些大神. OK,开始今天的主题. 关于MVP模式与MVVM模式孰是孰非,我想不是我一个初学者短短几句就能够说清楚的,我们也不用纠结于此.引用大

从最简单的HelloWorld理解MVP模式

大多数编程语言相关的学习书籍,都会以hello,world这个典型的程序作为第一个示例.作为Android应用开发者,无论使用eclipse还是用android studio,在新建项目的时候,一直按IDE默认选择项,下一步进行下去,就会创建出一个可以运行的hello,world应用程序.对于这个程序,可以认为是采用MVC模式,对应关系为: View:对应于布局文件 Model:业务逻辑和实体模型 Controller:对应于Activity   但是数据绑定.事件处理(hello world程

重构与模式:改善代码三部曲中的第三部

一.改善代码的三部曲 <设计模式>-> <重构>-> <重构与模式>.也就是设计->重构->重构出新设计. <设计模式>主要详细说明20几种模式,为我们带来了常见设计问题的经典解决方案,从而改变了整个面向对象开发的面貌.为设计而著. <重构>改善既有代码的设计,总结了我们会用到的各种重构手法,为我们带来了一种改进代码的高效过程,从而彻底改变了面向对象设计的方式.侧重去除坏代码的味道. <重构与模式>是设计模式相

MVP模式是否适合我们使用? 代码说话

转载请注明出处:王亟亟的大牛之路 按照惯例今天上了一个关于沉浸式菜单栏的文章的就不写了,然后 下班前看到一些感兴趣的内容就研究了下,MVP模式之前有大致的了解但是没有实战的在安卓上用过.(国内也有一些大牛有些过类似的内容,但是每个人对一件事情的理解可能大致相同但是总有细微的差异,主要是把我对这件东西的理解和实现方式分享给大家) 我不太喜欢搬太多人家已经整理好的东西再贴出来搞的自己理论多牛B,我还是用代码和例子说话,那么这里留下一些别人讲过的概念性的东西给大家建立概念,这些设计层面的东西还是希望大

重构遗留代码(1):金牌大师

旧代码,丑陋的代码,复杂的代码,意大利面条似的代码,鬼话废话……就是四个字:遗留代码.这是一个系列文章,将有助于你处理并解决它. 在理想的世界中,你只会写新代码.你会把代码写得既漂亮又完美.你将永不会再看你的代码,并且你将永远不会维护一个有十年之久的项目.在理想的世界中… 不幸的是,我们生活在现实的而非理想的世界.我们必须理解修改和增强年代久远的代码这件事.我们必须处理遗留代码.那么你还在等什么?让我们一头扎进第一篇教程,拿着代码,读懂一点点,并为了我们日后的修改编织一张安全网. 遗留代码的定义

关于Android MVP模式的思考

最近经常看到各种介绍MVP模式的博客的,之前写过不少的Android应用,在做那些应用的时候,都是要求快速完成,所以从开始设计到写代码就一直考虑着重用.以前写的项目基本都是不断重构项目,将项目代码变得更加精简,提高代码之间的复用性.但是代码并没有特别地注重按照MVC模式或者是MVP模式来,更多的是直接考虑模块化,重用,精简.所以看了MVP模式后,决定去总结一下自己代码中的问题并优化,算是对自己之前写的代码的回顾. MVP框架 MVP框架是目前在Android流行起来的框架,它非常适合用于Andr

android MVP模式简单介绍

原文 http://zhengxiaopeng.com/2015/02/06/Android%E4%B8%AD%E7%9A%84MVP/ 前言 MVP作为一种MVC的演化版本在Android开发中受到了越来越多的关注,但在项目开发中选择一种这样的软件设计模式需保持慎重心态,一旦确定 使用MVP作为你App的开发模式那么你就最好坚持做下去,如果在使用MVP模式开发过程中发现问题而且坑越来越大,这时你想用MVC等来重新设计的话基 本上就等于推倒重来了.要知道在Android上MVP在现在为止并没有统

MVP模式入门案例

随着UI创建技术的功能日益增强,UI层也履行着越来越多的职责.为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数据的可视化以及与用户的交互,同时让Model只关系数据的处理,基于MVC概念的MVP(Model-View-Presenter)模式应运而生. 四个要素: (1)View:负责绘制UI元素.与用户进行交互(在Android中体现为Activity); (2)View interface:需要View实现的接口,View通过View interface与Pr

对历史遗留代码的维护和再开发

一.时间宽松时的代码维护 对于新人,一般都会留出一段时间进行代码的接手,那么对于如何处理接到手中的代码,是不是只是看看代码.写写心得,还是能够利用这段空闲时间,煅炼提升自己,我期望是后者,结合公司部门的情况,对这种情况进行个人阐释: 1.对现有代码的熟悉.若是对拿到手中的代码,都不知道是做什么的,有什么用,可能就什么意义也没有,后面的也不用看了. 2.加入或者补充测试用例,若是没有测试用例,后面的重构基本上是没办法进行判断是否正确的. 3.用测试用例对代码进行测试,以期达到相关效果和理解代码逻辑