切图崽的自我修养-[MVVM] Js MV*模式浅谈

前言

做客户端开发、前端开发对MVC、MVP、MVVM这些名词不了解也应该大致听过,都是为了解决图形界面应用程序复杂性管理问题而产生的应用架构模式。网上很多文章关于这方面的讨论比较杂乱,各种MV模式之间的区别分不清,甚至有些描述都是错误的。本文追根溯源,从最经典的Smalltalk-80 MVC模式开始逐步还原图形界面之下最真实的MV模式。


GUI程序所面临的问题

图形界面的应用程序提供给用户可视化的操作界面,这个界面提供给数据和信息。用户输入行为(键盘,鼠标等)会执行一些应用逻辑,应用逻辑(application logic)可能会触发一定的业务逻辑(business logic)对应用程序数据的变更,数据的变更自然需要用户界面的同步变更以提供最准确的信息。例如用户对一个电子表格重新排序的操作,应用程序需要响应用户操作,对数据进行排序,然后需要同步到界面上。

在开发应用程序的时候,以求更好的管理应用程序的复杂性,基于职责分离(Speration of Duties)的思想都会对应用程序进行分层。在开发图形界面应用程序的时候,会把管理用户界面的层次称为View,应用程序的数据为Model(注意这里的Model指的是Domain Model,这个应用程序对需要解决的问题的数据抽象,不包含应用的状态,可以简单理解为对象)。Model提供数据操作的接口,执行相应的业务逻辑。

有了View和Model的分层,那么问题就来了:View如何同步Model的变更,View和Model之间如何粘合在一起。

带着这个问题开始探索MV模式,会发现这些模式之间的差异可以归纳为对这个问题处理的方式的不同。而几乎所有的MV模式都是经典的Smalltalk-80 MVC的修改版。

Smalltalk-80 MVC

历史背景

早在上个世纪70年代,美国的施乐公司(Xerox)的工程师研发了Smalltalk编程语言,并且开始用它编写图形界面的应用程序。而在Smalltalk-80这个版本的时候,一位叫Trygve Reenskaug的工程师设计了MVC图形应用程序的架构模式,极大地降低了图形应用程序的管理难度。而在四人帮(GoF)的设计模式当中并没有把MVC当做是设计模式,而仅仅是把它看成解决问题的一些类的集合。Smalltalk-80 MVC和GoF描述的MVC是最经典的MVC模式。

MVC的依赖关系

MVC出了把应用程序分成View、Model层,还额外的加了一个Controller层,它的职责为进行Model和View之间的协作(路由、输入预处理等)的应用逻辑(application logic);Model进行处理业务逻辑。Model、View、Controller三个层次的依赖关系如下:

Controller和View都依赖Model层,Controller和View可以互相依赖。在一些网上的资料Controller和View之间的依赖关系可能不一样,有些是单向依赖,有些是双向依赖,这个其实关系不大,后面会看到它们的依赖关系都是为了把处理用户行为触发的事件处理权交给Controller。

MVC的调用关系

用户的对View操作以后,View捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller会对来自View数据进行预处理、决定调用哪个Model的接口;然后由Model执行相关的业务逻辑;当Model变更了以后,会通过观察者模式(Observer Pattern)通知View;View通过观察者模式收到Model变更的消息以后,会向Model请求最新的数据,然后重新更新界面。如下图:

看似没有什么特别的地方,但是由几个需要特别关注的关键点:

  1. View是把控制权交移给Controller,Controller执行应用程序相关的应用逻辑(对来自View数据进行预处理、决定调用哪个Model的接口等等)。
  2. Controller操作Model,Model执行业务逻辑对数据进行处理。但不会直接操作View,可以说它是对View无知的。
  3. View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对视图进行更新。

需要特别注意的是MVC模式的精髓在于第三点:Model的更新是通过观察者模式告知View的,具体表现形式可以是Pub/Sub或者是触发Events。而网上很多对于MVC的描述都没有强调这一点。通过观察者模式的好处就是:不同的MVC三角关系可能会有共同的Model,一个MVC三角中的Controller操作了Model以后,两个MVC三角的View都会接受到通知,然后更新自己。保持了依赖同一块Model的不同View显示数据的实时性和准确性。我们每天都在用的观察者模式,在几十年前就已经被大神们整合到MVC的架构当中。

这里有一个MVC模式的JavaScript Demo,实现了一个小的TodoList应用程序。经典的Smalltalk-80 MVC不需要任何框架支持就可以实现。目前Web前端框架当中只有一个号称是严格遵循Smalltalk-80 MVC模式的:maria.js。

MVC的优缺点

优点:

  1. 把业务逻辑和展示逻辑分离,模块化程度高。且当应用逻辑需要变更的时候,不需要变更业务逻辑和展示逻辑,只需要Controller换成另外一个Controller就行了(Swappable Controller)。
  2. 观察者模式可以做到多视图同时更新。

缺点:

1.Controller测试困难。因为视图同步操作是由View自己执行,而View只能在有UI的环境下运行。在没有UI环境下对Controller进行单元测试的时候,应用逻辑正确性是无法验证的:Model更新的时候,无法对View的更新操作进行断言。
2.View无法组件化。View是强依赖特定的Model的,如果需要把这个View抽出来作为一个另外一个应用程序可复用的组件就困难了。因为不同程序的的Domain Model是不一样的


MVC Model 2

在Web服务端开发的时候也会接触到MVC模式,而这种MVC模式不能严格称为MVC模式。经典的MVC模式只是解决客户端图形界面应用程序的问题,而对服务端无效。服务端的MVC模式又自己特定的名字:MVC Model 2,或者叫JSP Model 2,或者直接就是Model 2 。Model 2客户端服务端的交互模式如下:

服务端接收到来自客户端的请求,服务端通过路由规则把这个请求交由给特定的Controller进行处理,Controller执行相应的应用逻辑,对Model进行操作,Model执行业务逻辑以后;然后用数据去渲染特定的模版,返回给客户端。

因为HTTP协议是单工协议并且是无状态的,服务器无法直接给客户端推送数据。除非客户端再次发起请求,否则服务器端的Model的变更就无法告知客户端。所以可以看到经典的Smalltalk-80 MVC中Model通过观察者模式告知View更新这一环被无情地打破,不能称为严格的MVC。

Model 2模式最早在1998年应用在JSP应用程序当中,JSP Model 1应用管理的混乱诱发了JSP参考了客户端MVC模式,催生了Model 2。

后来这种模式几乎被应用在所有语言的Web开发框架当中。PHP的ThinkPHP,Python的Dijango、Flask,NodeJS的Express,Ruby的RoR,基本都采纳了这种模式。平常所讲的MVC基本是这种服务端的MVC。


MVP

MVP模式有两种:

  1. Passive View
  2. Supervising Controller

而大多数情况下讨论的都是Passive View模式。本文会对PV模式进行较为详细的介绍,而SC模式则简单提及。

历史背景

MVP模式是MVC模式的改良。在上个世纪90年代,IBM旗下的子公司Taligent在用C/C++开发一个叫CommonPoint的图形界面应用系统的时候提出来的。

MVP(Passive View)的依赖关系

MVP模式把MVC模式中的Controller换成了Presenter。MVP层次之间的依赖关系如下:

MVP打破了View原来对于Model的依赖,其余的依赖关系和MVC模式一致。

MVP(Passive View)的调用关系

既然View对Model的依赖被打破了,那View如何同步Model的变更?看看MVP的调用关系:

和MVC模式一样,用户对View的操作都会从View交移给Presenter。Presenter会执行相应的应用程序逻辑,并且对Model进行相应的操作;而这时候Model执行完业务逻辑以后,也是通过观察者模式把自己变更的消息传递出去,但是是传给Presenter而不是View。Presenter获取到Model变更的消息以后,通过View提供的接口更新界面。

关键点:

  1. View不再负责同步的逻辑,而是由Presenter负责。Presenter中既有应用程序逻辑也有同步逻辑。
  2. View需要提供操作界面的接口给Presenter进行调用。(关键)
    对比在MVC中,Controller是不能操作View的,View也没有提供相应的接口;

而在MVP当中,Presenter可以操作View,View需要提供一组对界面操作的接口给Presenter进行调用;Model仍然通过事件广播自己的变更,但由Presenter监听而不是View。

MVP(Passive View)的优缺点

优点:

  1. 便于测试。Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。这里根据上面的例子给出了Presenter的单元测试样例。
  2. View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。

缺点:

  1. Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。

MVP(Supervising Controller)

上面讲的是MVP的Passive View模式,该模式下View非常Passive,它几乎什么都不知道,Presenter让它干什么它就干什么。而Supervising Controller模式中,Presenter会把一部分简单的同步逻辑交给View自己去做,Presenter只负责比较复杂的、高层次的UI操作,所以可以把它看成一个Supervising Controller。

Supervising Controller模式下的依赖和调用关系:

因为Supervising Controller用得比较少,对它的讨论就到这里为止


MVVM

历史背景

MVVM模式最早是微软公司提出,并且了大量使用在.NET的WPF和Sliverlight中。2005年微软工程师John Gossman在自己的博客上首次公布了MVVM模式。

ViewModel

MVVM代表的是Model-View-ViewModel,这里需要解释一下什么是ViewModel。ViewModel的含义就是 "Model of View",视图的模型。它的含义包含了领域模型(Domain Model)和视图的状态(State)。 在图形界面应用程序当中,界面所提供的信息可能不仅仅包含应用程序的领域模型。还可能包含一些领域模型不包含的视图状态,例如电子表格程序上需要显示当前排序的状态是顺序的还是逆序的,而这是Domain Model所不包含的,但也是需要显示的信息。

可以简单把ViewModel理解为页面上所显示内容的数据抽象,和Domain Model不一样,ViewModel更适合用来描述View。

MVVM的依赖

MVVM的依赖关系和MVP依赖,只不过是把P换成了VM。

MVVM的调用关系

MVVM的调用关系和MVP一样。但是,在ViewModel当中会有一个叫Binder,或者是Data-binding engine的东西。以前全部由Presenter负责的View和Model之间数据同步操作交由给Binder处理。你只需要在View的模版语法当中,指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对进行Model更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入),Binder也会自动把数据更新到Model上去。这种方式称为:Two-way data-binding,双向数据绑定。可以简单而不恰当地理解为一个模版引擎,但是会根据数据变更实时渲染。

也就是说,MVVM把View和Model的同步逻辑自动化了。以前Presenter负责的View和Model同步不再手动地进行操作,而是交由框架所提供的Binder进行负责。只需要告诉Binder,View显示的数据对应的是Model哪一部分即可。

MVVM的优缺点

优点:

  1. 提高可维护性。解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制。提高了代码的可维护性。
  2. 简化测试。因为同步逻辑是交由Binder做的,View跟着Model同时变更,所以只需要保证Model的正确性,View就正确。大大减少了对View同步更新的测试。

缺点:

  1. 过于简单的图形界面不适用,或说牛刀杀鸡。
  2. 对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。
  3. 数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的。

结语

可以看到,从MVC->MVP->MVVM,就像一个打怪升级的过程。后者解决了前者遗留的问题,把前者的缺点优化成了优点。同样的Demo功能,代码从最开始的一堆文件,优化成了最后只需要20几行代码就完成。MV*模式之间的区分还是蛮清晰的,希望可以给对这些模式理解比较模糊的同学带来一些参考和思路。

本文转载自界面之下:还原真实的MV*模式

原文地址:https://www.cnblogs.com/jlfw/p/12597144.html

时间: 2024-10-19 02:19:08

切图崽的自我修养-[MVVM] Js MV*模式浅谈的相关文章

切图崽的自我修养-优化图片加载流程

前言 优化! 又是优化! 切图崽们作为整个web应用的纽带,连接着用户行为和机器性能. 而优化的最终意义,在于在这两者之间取得一个最佳的平衡点. 对于图片资源的加载来说,更是如此. 今天我们就来简单说说,项目开发中常见的图片加载优化方式. 预加载 1.遮罩大法 我们经常用jquery, jquery中$(function){})实际上是DOMContentLoaded事件完成的回调,只是完成了DOM树的构建. 诸如Css的渲染以及页面内图片等资源的下载不一定完成了.所以如果此时呈现页面,页面会非

切图崽的自我修养-[ES6] 异步函数管理方案浅析

前言 业务开发中经常会用到异步函数,这里简单的对异步函数以及它的各种各样的解决方案做一个浅析 优缺点: 优点: 能够极大的提高程序并发业务逻辑的能力. 缺点: 异步函数的书写方式和代码执行逻辑很不直观,回调函数这种方式不太符合人类的的线性思维 异步函数的执行流程通常不好管理 不好对异步函数部署错误处理机制 解决方案 针对异步函数存在的缺点,所以才有了形形色色的异步的处理方案,常见的比如 原生的回调函数 promise/A+ async/await(generator); 业务场景 但这些解决方案

js 函数式编程 浅谈

js 函数式编程 函数式的思想, 就是不断地用已有函数, 来组合出新的函数. 函数式编程具有五个鲜明的特点: 1. 函数是"第一等公民" 指的是函数与其他数据类型一样,处于平等地位 2. 只用"表达式",不用"语句" "表达式"(expression)是一个单纯的运算过程,总是有返回值: "语句"(statement)是执行某种操作,没有返回值. 3. 没有"副作用" 指的是函数内部与外

Node.JS + MongoDB技术浅谈

看到一个Node.JS + MongoDB的小样例,分享给大家.魔乐科技软件学院(www.mldnjava.cn)的讲座 Node.JS + MongoDB技术讲座          云计算 +大数据 = 未来. 在中国的云计算上基本上是一个概念,个人感觉与当初的SOA没有太大的差别,空泛的理论. 中小型开发的未来 = Node.JS + MongoDB解决,并非说Java就没用了. 1.在前面的话 Java技术始终作为高端软件用户的首选,仅仅要从事大型的软件开发,那么Java都是必需要使用的技

[JS学习笔记]浅谈Javascript事件模型

DOM0级事件模型 element.on[type] = function(){} 兼容性:全部支持 lay1 lay2 lay3 e.target:直接触发事件的元素[IE8及以下不支持taget属性,使用e.srcElement代替] e.srcElement:直接触发事件的元素[FF不支持srcElement属性,使用e.target代替] e.currentTarget:被间接触发的元素[IE8及以下不支持currentTarget属性,使用this代替] this:同e.current

浅谈Html的内容加载及JS执行顺序

同事跟我说他用jQuery取不到页面上隐藏元素input的值,他的html页面大概内容如下. <!DOCTYPE html> <html lang="zh"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <script type="text/javascript"

fabric.js的简单上手及基于fabric.js的canvas切图工具:1、基本使用及配置

参考链接 Fabric.js 简单介绍和使用 简介 Fabric.js是一个可以简化canvas程序编写的库. Fabric.js为canvas提供所缺少的对象模型, svg parser, 交互和一整套其他不可或缺的工具.基于MIT协议开源,在github上有许多人贡献代码. 为什么选择fabric.js 手上的项目必须使用canvas 原生的canvas API不够友好 用fabric.js实现切图用户体验更好 为项目以后的迭代留下扩展的余地 简单上手 请参考文章开头的参考链接 这里主要介绍

在vue中继续使用layer.js来做弹出层---切图网

layer.js是一个方便的弹出层插件,切图网专注于PSD2HTML等前端切图多年,后转向Vue开发.在vue开发过程中引入layer.js的时候遇到了麻烦.原因是layer.js不支持import导入,这时就需要修改一下它的源码.在看过它的源码后,发现需要修改的地方只有两处, 源码中已经暴露了一个全局变量layer,故只需在脚本末尾处添加 export default layer; 这表示将这个全局变量导出. 然后在文件中找到下面的代码注释掉,这段代码是为layer添加样式的,但使用的路径不对

前端开发工程师必备JS技能-切图

/******************************************** 学习时间:2015年12月21日 学习者:易天曦 学习目的:掌握切图技巧 学习目标:1.学会从网页设计师的PSD文件中提取素材 2.对PS有一定的了解 3.对审美有一定的提高 *******************************************/ 学习内容: 1.网页设计中初始化尺寸参数和一些快捷键. 2.选择和移动元素. 3.常见的Web切图界面设置.