当前位置:   article > 正文

MVC、MVP、MVVM模式简介及区别_mvcmvpmvvm三种模型的区别?

mvcmvpmvvm三种模型的区别?

MVC,MVP,MVVM是三种常见的前端架构模式,通过分离关注点来改进代码组织方式。MVC模式是MVP,MVVM模式的基础,这两种模式更像是MVC模式的优化改良版,他们三个的MV即Model,view相同,不同的是MV之间的纽带部分。

Model:模型】指的是后端传递的数据。注意这里的M指的是Domain Model,这个应用程序对需要解决的问题的数据抽象,不包含应用的状态,可以简单理解为对象,只保存数据结构和提供数据操作的接口。

View:【视图】指的是所看到的页面。

Controller:控制器指的是页面业务逻辑。

Mvvm定义:Model-View-ViewModel的简写。即模型-视图-视图模型。

【视图模型】mvvm模式的核心,它是连接view和model的桥梁。两个方向称之为数据的双向绑定:

一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。

二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。

总结:在MVVM的框架下视图和模型是不能直接通信的。它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View 和 ViewModel可以互相通信。MVVM流程图如下:

 

MVC的定义:Model-View- Controller的简写。即模型-视图-控制器。使用MVC的目的就是将M和V的代码分离。MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。

MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。

如果前端没有框架,只使用原生的html+js,MVC模式可以这样理解:将html看成view;js看成controller,将js的ajax当做Model,也就是数据层,通过ajax从服务器获取数据。

简单来说:

1、MVC:(非服务端的MVC)

  • View接受用户的交互请求
  • View将请求转交给Controller
  • Controller操作Model进行数据更新
  • 数据更新之后,Model通知View数据变化
  • View显示更新之后的数据(主动获取model的变化数据)

注意:上面流程是单向数据流动的,controller只是接收View的指令,更新后的数据不是通过controller通知view更新,而是model通知的=》单向的

流程描述:用户对View操作以后,View捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller接着会执行相关的业务逻辑,这些业务逻辑可能需要对Model进行相应的操作;当Model变更了以后,会通过观察者模式(Observer Pattern)通知View;View通过观察者模式收到Model变更的消息以后,会向Model请求最新的数据,然后重新更新界面。

1、View是把控制权交移给Controller,自己不执行业务逻辑。

2、Controller执行业务逻辑并且操作Model,但不会直接操作View,可以说它是对View无知的。

3、View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对视图进行更新。

优点:

1、把业务逻辑全部分离到Controller中,模块化程度高。当业务逻辑变更的时候,不需要变更View和Model,只需要Controller换成另外一个Controller就行了(Swappable Controller)。

2、观察者模式可以做到多视图同时更新。

缺点:

1、Controller测试困难。因为视图同步操作是由View自己执行,而View只能在有UI的环境下运行。在没有UI环境下对Controller进行单元测试的时候,Controller业务逻辑的正确性是无法验证的:Controller更新Model的时候,无法对View的更新操作进行断言。

2、View无法组件化。View是强依赖特定的Model的,如果需要把这个View抽出来作为一个另外一个应用程序可复用的组件就困难了。因为不同程序的的Domain Model是不一样的

简单案例:https://github.com/livoras/MVW-demos/tree/master/src/scripts/mvc

  1. models:todos.js
  2. var EventEmitter = require("eventemitter2").EventEmitter2
  3. function TodosModel() {
  4. EventEmitter.call(this)
  5. this._todos = [{content: "Make PPT!", done: false}]
  6. }
  7. var pro = TodosModel.prototype = Object.create(EventEmitter.prototype)
  8. pro.getTodos = function() {
  9. return this._todos
  10. }
  11. pro.setTodos = function(todos) {
  12. this._todos = todos
  13. this.emit("change", todos) //3、和view配合,通知view数据更新了
  14. }
  15. module.exports = TodosModel
  16. views:todos-list-view.js
  17. var Handlebars = require("handlebars")
  18. function TodosListView(controller, model) {
  19. this.controller = controller
  20. this.model = model
  21. this.template = Handlebars.compile($("#todos-list-tpl").html())
  22. this.$el = $("<div></div>")
  23. }
  24. TodosListView.prototype.build = function() {
  25. this.render()
  26. this.listenCheck()
  27. this.listenModel()
  28. }
  29. TodosListView.prototype.render = function() {
  30. var todos = this.model.getTodos()
  31. this.$el.html(this.template({todos: todos}))
  32. }
  33. TodosListView.prototype.listenCheck = function() {
  34. var self = this
  35. self.$el.on("click", "li", null, function(event) {
  36. var $li = $(event.currentTarget)
  37. self.controller.onCheck(+$li.attr("data-index")) //1、将请求转发给controller
  38. })
  39. }
  40. TodosListView.prototype.listenModel = function() {
  41. this.model.on("change", this.render.bind(this)) //4、监控model变化主动获取model更新后的数据
  42. }
  43. module.exports =TodosListView
  44. controllers:todos-list-controller.js
  45. function TodosListController(model) {
  46. this.model = model
  47. }
  48. TodosListController.prototype.onCheck = function(index) {
  49. var todos = this.model.getTodos()
  50. var todo = todos[index]
  51. todo.done = !todo.done
  52. this.model.setTodos(todos) //2、Controller操作model去更新数据
  53. }
  54. module.exports = TodosListController

2、MVP:

和MVC相比,MVP打破了View原来对于Model的依赖。

  • View接受用户的交互请求
  • View将请求转交给Presenter
  • Presenter操作Model进行数据库更新
  • 数据更新之后,Model通知Presenter数据发生变化
  • Presenter更新View的数据

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

Presenter将Model的变化返回给View。和MVC不同的是,Presenter会反作用于View,不像Controller只会被动的接受View的指挥。抽象View暴露其属性和事件,然后Presenter引用View的抽象。这样可以很容易的构造View的Mock对象,提高可单元测试性。Presenter不仅要操作数据,而且要更新View。

1、View不再负责同步的逻辑,而是由Presenter负责。Presenter中既有业务逻辑也有同步逻辑。

2、View需要提供操作界面的接口给Presenter进行调用。(关键)

优点:

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的实现会根据View的充、贫血而有一些不同,一部分倾向于在View中放置简单的逻辑,在Presenter放置复杂的逻辑;另一部分倾向于在presenter中放置全部的逻辑。这两种分别被称为:Passive View(常讨论的是PV)和Superivising Controller。

Passive View模式,该模式下View非常Passive,它几乎什么都不知道,Presenter让它干什么它就干什么=》在Passive View中,为了减少UI组件的行为,使用Controller不仅控制用户事件的响应,而且将结果更新到View上。可以集中测试Controller,减小View出问题的风险。

Supervising Controller模式中,Presenter会把一部分简单的同步逻辑交给View自己去做,Presenter只负责比较复杂的、高层次的UI操作。

简单案例:https://github.com/livoras/MVW-demos/tree/master/src/scripts/mvp

  1. models:todos.js
  2. var EventEmitter = require("eventemitter2").EventEmitter2
  3. function TodosModel() {
  4. EventEmitter.call(this)
  5. this._todos = [{content: "Make PPT!", done: false}]
  6. }
  7. var pro = TodosModel.prototype = Object.create(EventEmitter.prototype)
  8. pro.getTodos = function() {
  9. return this._todos
  10. }
  11. pro.setTodos = function(todos) {
  12. this._todos = todos
  13. this.emit("change", todos) //通知presenter而不是view数据更新了
  14. }
  15. module.exports = TodosModel
  16. presenter:todos-list-presenter.js
  17. function TodosListPresenter(view, model) {
  18. this.view = view
  19. this.model = model
  20. this.init()
  21. }
  22. TodosListPresenter.prototype.init = function() {
  23. this.view.setPresenter(this)
  24. this.view.build(this.model.getTodos())
  25. this.listenModelChange()
  26. }
  27. TodosListPresenter.prototype.onCheck = function(index) {
  28. this.toggleTodoModel(index) //2、
  29. this.toggleTodoView(index) //
  30. }
  31. TodosListPresenter.prototype.toggleTodoModel = function(index) {
  32. var todos = this.model.getTodos() //2-1、更新model数据
  33. var todo = todos[index]
  34. todo.done = !todo.done
  35. }
  36. TodosListPresenter.prototype.toggleTodoView = function(index) {
  37. if (this.model.getTodos()[index].done) { //根据model数据情况,调用view暴露的相应方法更新view
  38. this.view.checkItem(index)
  39. } else {
  40. this.view.uncheckItem(index)
  41. }
  42. }
  43. TodosListPresenter.prototype.listenModelChange = function() {
  44. var self = this
  45. this.model.on("change", function() { //获取model数据的更新,通过调用view暴露的方法同步更新view
  46. self.view.render(self.model.getTodos())
  47. })
  48. }
  49. module.exports = TodosListPresenter
  50. views:todos-list-view.js
  51. var Handlebars = require("handlebars")
  52. function TodosListView(model) {
  53. this.presenter = null
  54. this.model = model
  55. this.template = Handlebars.compile($("#todos-list-tpl").html())
  56. this.$el = $("<div></div>")
  57. }
  58. TodosListView.prototype.build = function(todos) {
  59. this.render(todos)
  60. this.listen()
  61. }
  62. TodosListView.prototype.render = function(todos) {
  63. this.$el.html(this.template({todos: todos}))
  64. }
  65. TodosListView.prototype.setPresenter = function(presenter) {
  66. this.presenter = presenter
  67. }
  68. TodosListView.prototype.listen = function() {
  69. var self = this
  70. self.$el.on("click", "li", null, function(event) {
  71. var $li = $(event.currentTarget)
  72. self.presenter.onCheck(+$li.attr("data-index")) //1、转发给Presenter
  73. })
  74. }
  75. TodosListView.prototype.checkItem = function(i) {
  76. return this.$el.find("li").eq(i).addClass("done")
  77. }
  78. TodosListView.prototype.uncheckItem = function(i) {
  79. return this.$el.find("li").eq(i).removeClass("done")
  80. }
  81. module.exports = TodosListView

3、MVVM:

MVVM可以看作是一种特殊的MVP(Passive View)模式,或者说是对MVP模式的一种改良。

MVVM与MVC最大的区别就是:它实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,而是改变属性后该属性对应View层显示会自动改变。

MVVM是在原有领域Model的基础上添加一个ViewModel,ViewModel除了正常的属性以外,还包括一些供View显示用的属性。例如在经典的MVP中,View有一个属性IsCheck,需要在Presenter中设置View的IsCheck值。但是在MVVM中的Presenter也会有一个IsCheck属性来同步View的IsCheck属性,可能会用到Observer模式同步IsCheck的值。

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

优点:

1、提高可维护性。解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制。提高了代码的可维护性。

2、简化测试。因为同步逻辑是交由Binder做的,View跟着Model同时变更,所以只需要保证Model的正确性,View就正确。大大减少了对View同步更新的测试。

缺点:

1、过于简单的图形界面不适用,或说牛刀杀鸡。

2、对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。

3、数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的。

MVVM双向数据绑定典型代表VUE

 

参考:https://zhuanlan.zhihu.com/p/38108311

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/66206
推荐阅读
相关标签
  

闽ICP备14008679号