当前位置: 首页 > Web前端 > HTML

ChetuCubs的自我修养-【MVVM】JsMV-模式探讨

时间:2023-04-03 00:03:29 HTML

前言做客户端开发,前端开发,如果你对MVC、MVP、MVVM这几个名词不了解,你应该粗略听过,都其中主要是解决图形界面应用程序架构模式所产生的程序复杂性管理问题。网上很多文章在这方面的讨论比较杂乱,各种MV模式的区分不清,甚至有些描述是错误的。本文追根溯源,从最经典的Smalltalk-80MVC模式入手,逐步还原图形界面下最真实的MV模式。GUI程序面临的问题图形界面应用程序为用户提供了一个可视化的操作界面,它提供了数据和信息。用户的输入行为(键盘、鼠标等)会执行一些应用逻辑,而应用逻辑可能会触发某些业务逻辑(businesslogic)来改变应用数据。数据的变化自然需要用户界面的同步变化,以提供最准确的信息。例如,当用户重新排序电子表格时,应用程序需要响应用户操作,对数据进行排序,然后将其同步到界面。在开发应用程序时,为了更好地管理应用程序的复杂性,会基于职责分离(SperationofDuties)的思想对应用程序进行分层。在开发图形界面应用时,管理用户界面的层次称为View,应用的数据是Model(注意这里的Model指的是DomainModel。这个应用抽象了要解决的问题的数据,not包含应用程序的状态,可以简单理解为一个对象)。模型提供数据操作的接口,并执行相应的业务逻辑。随着View和Model的分层,问题就来了:View如何同步Model的变化,View和Model又是如何绑定在一起的。带着这个问题开始探索MV模式,你会发现这些模式之间的差异可以概括为处理这个问题的不同方式。并且几乎所有的MV模式都是经典Smalltalk-80MVC的修改版本。Smalltalk-80MVC历史背景早在20世纪70年代,美国施乐公司的工程师就开发了Smalltalk编程语言,并开始用它来编写图形界面应用程序。在Smalltalk-80版本中,一位名叫TrygveReenskaug的工程师设计了MVC图形应用的架构模式,大大降低了图形应用的管理难度。在四人帮(GoF)的设计模式中,MVC并没有被看作是一种设计模式,而仅仅是解决问题的类的集合。GoF描述的Smalltalk-80MVC和MVC是最经典的MVC模式。MVC的DependenciesMVC将应用程序分为View和Model层,并额外增加了一个Controller层,负责Model和View之间协作的应用逻辑(application)(路由、输入预处理等)逻辑);模型处理业务逻辑。Model、View、Controller这三个层次的依赖关系如下:Controller和View都依赖于Model层,Controller和View可以相互依赖。在一些线上资料中,Controller和View之间的依赖关系可能是不一样的。有些是单向依赖,有些是双向依赖。处理事件的权利交给了Controller。MVC的调用关系用户对View进行操作后,View捕获这个操作,将处理权交给Controller(Passcalls);Controller会预处理来自View的数据,并决定调用哪个Model接口;然后Model执行相关的业务逻辑;当Model发生变化时,View会通过观察者模式(ObserverPattern)得到通知;View通过ObserverPattern接收到Model变化的消息后,会向Model请求最新的数据,然后重新更新界面。如下图所示:看似没什么特别的,但是有几个关键点需要特别注意:View将控制权交给Controller,Controller执行应用程序相关的应用逻辑(预处理来自View的数据,决定哪个Model接口打电话等)。Controller操作Model,Model执行业务逻辑处理数据。但是它并没有直接操作View,所以可以说是对View一无所知。View和Model的同步消息是通过观察者模式进行的,同步操作就是View自己去请求Model的数据,然后更新view。需要注意的是,MVC模式的本质在于第三点:Model的更新是通过观察者模式通知给View的,具体的表现形式可以是Pub/Sub或者triggerEvents。网上很多对MVC的描述都没有强调这一点。使用观察者模式的好处是不同的MVC三角可能有一个共同的Model。一个MVC三角形中的Controller操作完Model后,两个MVC三角形中的View会收到通知并自行更新。它保持了依赖于同一模型的不同视图显示数据的实时性和准确性。我们日常使用的观察者模式,早在几十年前就已经被大师们融入到MVC架构中了。这是MVC模式的JavaScript演示,实现了一个小型TodoList应用程序。经典的Smalltalk-80MVC可以在没有任何框架支持的情况下实现。目前,声称严格遵循Smalltalk-80MVC模型的前端Web框架只有一个:maria.js。MVC的优缺点优点:业务逻辑和显示逻辑分离,模块化程度高。而当应用逻辑需要改变时,不需要改变业务逻辑和显示逻辑,只需要将Controller换成另一个Controller(SwappableController)即可。观察者模式可以同时更新多个视图。缺点:1.控制器测试困难。因为视图同步操作是由View自己来完成的,而View只能运行在有UI的环境中。在没有UI环境的情况下对Controller进行单元测试时,无法验证应用逻辑的正确性:更新Model时,无法断言View的更新操作。2.View不能组件化。视图强烈依赖于特定的模型。很难将此视图提取为另一个应用程序的可重用组件。由于不同程序的DomainModel不同,MVCModel2在开发Web服务器时也会接触到MVC模型,而这种MVC模型严格来说不能称为MVC模型。经典的MVC模型只解决了客户端图形界面应用的问题,对于服务端是无效的。服务器端MVC模式有其特定的名称:MVCModel2,或JSPModel2,或简称Model2。Model2客户端服务器的交互方式如下:服务器接收到客户端的请求,server通过路由规则将请求传递给具体的Controller进行处理,Controller执行相应的应用逻辑对Model进行操作,Model执行业务逻辑后;然后使用数据渲染特定模板并将其返回给客户端。由于HTTP协议是单工协议,是无状态的,所以服务端不能直接向客户端推送数据。除非客户端再次发起请求,否则无法通知客户端服务器上Model的变化。所以可以看出,在经典的Smalltalk-80MVC中,Model通过观察者模式通知View进行更新,被无情的打破了,不能称之为严格的MVC。Model2模式于1998年首次应用于JSP应用,JSPModel1应用管理的混乱导致JSP参考了客户端MVC模式,由此诞生了Model2。后来该模式几乎应用于所有Web所有语言的开发框架。PHP中的ThinkPHP,Python中的Dijango和Flask,NodeJS中的Express,Ruby中的RoR,基本上都是采用这种模型。我们平时说的MVC,基本上就是这种服务端MVC。有两种类型的MVPPMVP模式:PassiveViewSupervisingController,讨论的大多数情况都是PassiveView模式。本文将更详细地介绍PV模式,而SC模式将被简单提及。历史背景MVP模式是MVC模式的改进。1990年代,IBM的子公司Taligent在用C/C++开发名为CommonPoint的图形界面应用系统时提出了它。MVP(PassiveView)依赖MVP模式用Presenter代替了MVC模式中的Controller。MVP层级之间的依赖关系如下:MVP打破了View原有的对Model的依赖关系,其余的依赖关系与MVC模式保持一致。MVP(PassiveView)的调用关系既然打破了View对Model的依赖,那么View如何同步Model的变化呢?再看MVP的调用关系:和MVC模式一样,用户对View的操作都会从View交给Presenter。Presenter会执行相应的应用逻辑,对Model进行相应的操作;这时候Model执行完业务逻辑后,也会通过观察者模式传递自己变化的消息,只不过是传递给Presenter,而不是View。Presenter获取到Model变化的消息后,通过View提供的接口更新接口。重点:View不再负责同步的逻辑,而是Presenter。Presenter中既有应用逻辑也有同步逻辑。View需要提供一个操作界面接口供Presenter调用。(重点)相比之下,在MVC中,Controller无法操作View,View也没有提供相应的接口;而在MVP中,Presenter可以操作View,View需要提供一套接口操作供Presenter调用;Model仍然通过事件广播您自己的更改,但由Presenter而不是View收听。MVP(PassiveView)的优缺点:容易测试。Presenter通过界面执行View,在对不依赖UI环境的Presenter进行单元测试时。可以mock一个View对象,这个对象只需要实现View的接口即可。然后将依赖项注入到Presenter中,单元测试时就可以完整测试Presenter应用逻辑的正确性。这是基于上述示例的Presenter单元测试示例。视图可以组件化。在MVP中,View不依赖于Model。这样View就可以脱离具体的业务场景。可以说View可以完全不懂业务。它只需要为上层操作提供一系列接口即可。这允许高度可重用的视图组件。缺点:Presenter除了应用逻辑,还有很多View->Model,Model->View手动同步逻辑,导致Presenter繁琐难维护。上面说的MVP(SupervisingController)就是MVP的PassiveView模式。在这种模式下,View是非常被动的。它几乎什么都不知道,它会做Presenter要求它做的任何事情。在SupervisingController模式下,Presenter会把部分简单的同步逻辑交给View本身,而Presenter只负责更复杂、高层的UI操作,所以可以看成是一个SupervisingController。SupervisingController模式下的依赖和调用关系:由于SupervisingController用的比较少,讨论到此为止2005年,微软工程师JohnGossman在他的博客上首次宣布了MVVM模式。ViewModelMVVM代表Model-View-ViewModel,这里需要说明一下什么是ViewModel。ViewModel的意思是“ModelofView”,视图的模型。它的含义包括领域模型(DomainModel)和视图的状态(State)。在图形界面应用程序中,界面提供的信息可能不仅仅包括应用程序的领域模型。它还可能包含一些未包含在域模型中的视图状态。例如,电子表格程序需要显示当前排序状态是顺序还是逆序。这个不包含在DomainModel中,但是也是需要展示的信息。ViewModel可以简单理解为对页面显示内容的数据抽象。与DomainModel不同,ViewModel更适合描述View。MVVM的依赖MVVM的依赖和MVP的依赖无非就是把P换成VM。MVVM的调用关系MVVM的调用关系和MVP是一样的。但是,在ViewModel中会有一个叫做Binder或Data-bindingengine的东西。之前由Presenter负责的View和Model之间的所有数据同步操作都交给了Binder。只需要在View的模板语法中强制声明View上显示的内容绑定Model中的哪条数据即可。当ViewModel更新Model时,Binder会自动更新数据到View,当用户对View进行操作(比如表单输入)时,Binder也会自动更新数据到Model。这种方法叫做:Two-waydata-binding,双向数据绑定。可以简单不恰当的理解为模板引擎,但是它会根据数据变化实时渲染。换句话说,MVVM自动化了View和Model的同步逻辑。以往由Presenter负责的View和Model的同步不再由人工操作,而是交给了框架提供的Binder。你只需要告诉Binder,View显示的数据对应于Model的哪一部分。MVVM的优点和缺点优点:提高可维护性。解决了MVP中大量手动View和Model同步的问题,提供双向绑定机制。改进了代码的可维护性。简化测试。因为同步逻辑交给了Binder,View和Model同时发生变化,所以你只需要保证Model的正确性,View就会正确。显着减少了对视图同步更新的测试。缺点:过于简单的图形界面不适用,或者大材小用。对于大型图形应用,视图状态较多,构建和维护ViewModel的成本会比较高。数据绑定的声明是用命令式的方式写在View的模板中的,调试这些内容是没有办法打断的。结语可以看到,从MVC->MVP->MVVM,就像是一个打怪升级的过程。后者解决了前者遗留的问题,将前者的不足优化为优势。同样的demo功能,代码从一开始的一堆文件优化到只需要20行代码就可以完成。MV*模式之间的区别比较明确,希望能给对这些模式认识模糊的同学带来一些参考和思路。本文转载自界面下:还原真实MV*模式