MVC

MVC最初是在研究Smalltalk-80(1979年)期间设计出来的,恐怕没有一本书能够回到计算机石器时代介绍一下Smalltalk的代码是如何实现MVC的,不仅如此,连想搞清楚当时的应用场景都很难了,都要追溯到80后出生以前的事了。在1995年出版的《设计模式:可复用面向对象软件的基础》对MVC进行了深入的阐述,在推广使用方面发挥了重要作用。

MVC包括三类对象,将他们分离以提高灵活性和复用性。

  • 模型 model 用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法,会有一个或多个视图监听此模型。一旦模型的数据发生变化,模型将通知有关的视图。

  • 视图 view 是它在屏幕上的表示,描绘的是model的当前状态。当模型的数据发生变化,视图相应地得到刷新自己的机会。

  • 控制器 controller 定义用户界面对用户输入的响应方式,起到不同层面间的组织作用,用于控制应用程序的流程,它处理用户的行为和数据model上的改变。

经典MVC模式inner

经典MVC模式

实线:方法调用 虚线:事件通知

其中涉及两种设计模式:

  • view 和 model 之间的观察者模式,view 观察 model,事先在此 model 上注册,以便 view 可以了解在数据 model 上发生的改变
  • view 和 controller 之间的策略模式

一个策略是一个表述算法的对象,MVC 允许在不改变视图外观的情况下改变视图对用户输入的响应方式。例如,你可能希望改变视图对键盘的响应方式,或希望使用弹出菜单而不是原来的命令键方式。MVC 将响应机制封装在 controller 对象中。存在着一个 controller 的类层次结构,使得可以方便地对原有的 controller 做适当改变而创建新的controller。

view 使用 controller 子类的实例来实现一个特定的响应策略。要实现不同的响应的策略只要用不同种类的 controller 实例替换即可。甚至可以在运行时刻通过改变 view 的 controller 来改变用户输入的响应方式。例如,一个 view 可以被禁止接受任何输入,只需给他一个忽略输入事件的 controller。

MVC for Javascript

javascript MVC模式inner

javascript MVC模式

如图所示,view 承接了部分 controller 的功能,负责处理用户输入,但是不必了解下一步做什么。它依赖于一个 controller 为它做决定或处理用户事件。事实上,前端的 view 已经具备了独立处理用户事件的能力,如果每个事件都要流经 controller ,势必增加复杂性。同时,view 也可以委托 controller 处理 model 的更改。model 数据变化后通知 view 进行更新,显示给用户。这个过程是一个圆,一个循环的过程。

这种从经典 MVC 到 Javascript MVC 的1对1转化,导致控制器的角色有点尴尬。MVC 这样的结构的正确性在于,任何界面都需要面对一个用户,而 controller “是用户和系统之间的链接”。在经典 MVC 中,controller 要做的事情多数是派发用户输入给不同的 view,并且在必要的时候从 view 中获取用户输入来更改 model,而 Web 以及绝大多数现在的UI系统中,controller 的职责已经被系统实现了。由于某种原因,控制器和视图的分界线越来越模糊,也有认为, view 启动了 action 理论上应该把 view 归属于 controller。比如在 Backbone 中,Backbone.View 和 Backbone.Router 一起承担了 controller 的责任。这就为 MVC 中 controller 的衍变埋下了伏笔。

MVP

MVP(Model-View-Presenter)是经典 MVC 设计模式的一种衍生模式,是在1990年代 Taligent 公司创造的,一个用于 C++ CommonPoint 的模型。背景上不再考证,直接上图看一下与MVC的不同。

MVP模式inner

MVP模式

经典 MVC 中,一对 controller-view 捆绑起来表示一个 ui 组件,controller 直接接受用户输入,并将输入转为相应命令来调用 model 的接口,对 model 的状态进行修改,最后通过观察者模式对 view 进行重新渲染。

进化为 MVP 的切入点是修改 controller-view 的捆绑关系,为了解决 controller-view 的捆绑关系,将进行改造,使 view 不仅拥有UI组件的结构,还拥有处理用户事件的能力,这样就能将 controller 独立出来。为了对用户事件进行统一管理,view 只负责将用户产生的事件传递给 controller,由 controller 来统一处理,这样的好处是多个 view 可共用同一个 controller。此时的 controller 也由组件级别上升到了应用级别,然而更新 view 的方式仍然与经典 MVC 一样:通过 Presenter 更新 model,通过观察者模式更新 view。

另一个显而易见的不同在于,MVC 是一个圆,一个循环的过程,但 MVP 不是,依赖 Presenter 作为核心,负责从 model 中拿数据,填充到 view 中。常见的 MVP 的实现是被动视图(passive view),Presenter 观察 model,不再是 view 观察 model,一旦 model 发生变化,就会更新 view。Presenter 有效地绑定了 model 到 view。view 暴露了 setters 接口以便 Presenter 可以设置数据。对于这种被动视图的结构,没有直接数据绑定的概念。但是他的好处是在 view 和 model 直接提供更清晰的分离。但是由于缺乏数据绑定支持,意味着不得不单独关注某个任务。在 MVP 里,应用程序的逻辑主要在 Presenter 来实现,其中的 view 是很薄的一层。

MVVM

MVVM,Model-View-ViewModel,最初是由 Microsoft 在使用 Windows Presentation Foundation 和 SilverLight 时定义的,2005年 John Grossman 在一篇关于 Avalon(WPF 的代号)的博客文章中正式宣布了它的存在。其中最重要的特性之一就是数据绑定(Data-binding)。

MVVM模式inner

MVVM模式

首先,view 和 model 是不知道彼此存在的,同 MVP 一样,将 view 和 model 清晰地分离开来。 其次,view 是对 viewmodel 的外在显示,与 viewmodel 保持同步,viewmodel 对象可以看作是 view 的上下文。view 绑定到 viewmodel 的属性上,如果 viewmodel 中的属性值变化了,这些新值通过数据绑定会自动传递给 view。反过来 viewmodel 会暴露 model 中的数据和特定状态给 view。 所以,view 不知道 model 的存在,viewmodel 和 model 也觉察不到 view。事实上,model 也完全忽略 viewmodel 和 view 的存在。这是一个非常松散耦合的设计。

MVVM的优缺点

优点:

  • 分离视图(View)和模型(Model),降低代码耦合,提⾼视图或者逻辑的重⽤性: ⽐如视图(View)可以独⽴于 Model 变化和修改,⼀个 ViewModel 可以绑定不同的 View 上,当 View 变化的时候 Model 不可以不变,当 Model 变化的时候 View 也可以不变。你可以把⼀些视图逻辑放在⼀个 ViewModel ⾥⾯,让很多 View 重⽤这段视图逻辑
  • ⾃动更新dom,提升开发效率: 利⽤双向绑,数据更新后视图⾃动更新,让开发者从繁琐的⼿动dom操作中解放
  • 提⾼可测试性: ViewModel 的存在可以帮助开发者更好地编写测试代码

缺点:

  • Bug很难被调试: 因为使⽤双向绑定的模式,当你看到界⾯异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得⼀个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的声明是指令式地写在 View 的模版当中的,这些内容是没办法去打断点 debug 的
  • ⼀个⼤的模块中 Model 也会很⼤,虽然使⽤⽅便了也很容易保证了数据的⼀致性,但如果⻓期持有,就会占用更多的内存
  • 对于⼤型的图形应⽤程序,视图状态较多,ViewModel 的构建和维护的成本都会⽐较⾼。