昨天有位同学私信我,说他所在的公司的不少项目都有个 controllers.py 的文件或者 controllers 的目录用来存放视图。但是和他理解的 Python Web 框架的设计模式不太一样,问我 MVC/MVT 到底怎么区分。听他一开始说,我为之一怔,因为我身边的人就有写 controllers 的... 不过我没有去验证这只是目录的名字而已。

今天我和大家聊聊我对这几个流行的设计模式的理解。

复杂的软件必须有清晰合理的架构,否则无法开发和维护。 - 阮一峰

MVC

MVC(Model View Controller 模型 - 视图 - 控制器)是一种 Web 架构的模式(本文不讨论桌面应用的 MVC),它把业务逻辑、模型数据、用户界面分离开来,让开发者将数据与表现解耦,前端工程师可以只改页面效果部分而不用接触后端代码,DBA 可以重新命名数据表并且只需更改一个地方,无需从一大堆文件中进行查找和替换。MVC 模式甚至还可以提高代码复用能力。现在几乎所有的 Web 开发框架都建立在 MVC 模式之上。 当然,最近几年也出现了一些诸如 MVP, MVVM 之类的新的设计模式。 但从技术的成熟程度和使用的广泛程度来讲,MVC 仍是主流。

MVC 三要素:

  1. Model(数据模型)。是对客观事物的抽象。比如上篇我们说到的知乎 Live,Live 就是一个模型,可以用 Live 类来表示。而一个模型通常还带有很多的和业务相关的逻辑,比如添加,更新,获取 Live 主讲人信息等等,这些组成了模型的方法。对于开发者模型的表示方法非常易懂和清晰,可以通过非常便捷的代码来做 CURD 操作而无需写一条又一条的 SQL 语句。
  2. View(视图)。呈现给用户的效果,呈现的内容是基于 Model,它也是收集用户输入的地方。比如看到一篇 Live,数据是一个 Live.get (live_id).to_dict () 的结果,效果是通过对应的模板和样式把这个数据展示出来。
  3. Contorller(控制器)。是 Model 和 View 之间的沟通者。 因为 View 中不会对 Model 作任何操作,Model 不会输出任何用于表现的东西,如 HTML 代码或者某种效果等,它就是点数据。而 Contorller 用于决定使用哪些 Model,对 Model 执行什么操作,为视图准备哪些数据,是 MVC 中沟通的桥梁。

MVC 的特点是通信单向的:

  1. 浏览器发送请求
  2. Contorller 和 Model 交互获取数据
  3. Contorller 调用 View
  4. View 渲染数据返回

更简单的表达式:V -> C-> M -> C -> V

MTV

和 Rails、Spring、Laravel 等其他语言的 Web 框架不一样,在 Python 的世界中,基本(除了 Pylons)都使用了 MVC 的变种 MTV(Model Templates View 模型 - 模板 - 视图):

  1. Model(数据模型)。和 MVC 的 Model 一样,处理与数据相关的所有事务:如何存取、如何确认有效性、包含哪些行为以及数据之间的关系等。
  2. Template(模板)。处理与表现相关的决定:如何在页面或其他类型文档中进行显示出来。
  3. View。处理业务逻辑,视图就是一个特定 URL 的回调函数,回调函数中描述数据:从 Model 取出对应的数据,调用相关的模板。它就是 Contorller 要调用的那个用来做 Model 和 View 之间的沟通函数,从而完成控制。

注意啦:MVC 中的 View 的目的是「呈现哪一个数据」,而 MTV 的 View 的目的是「数据如何呈现」。

也就是把 MVC 中的 View 分成了视图(展现哪些数据)和模板(如何展现)2 个部分,而 Contorller 这个要素由框架自己来实现了,我们需要做的就是把(带正则表达式的)URL 对应到视图就可以了,通过这样的 URL 配置,系统将一个请求发送到一个合适的视图。

需要注意,Flask 这种微框架就不是一个 MVC 模式的,因为它没有提供 Model,除非集成了 SQLAlchemy 之类的 ORM 进来。

所以大家最常见的 Python Web 项目的目录风格是这样的:

├── app.py
├── models  # 目录
├── requirements.txt
├── static
└── views  # 目录

或者:

├── app.py
├── models.py
├── requirements.txt
├── static
└── views.py

Django 官方网站《基于 python 的 web 应用开发实战》书中的项目 flaskyFlask 官方网站知名的 Django API 框架 等项目都是这种命名原则。

MVVM

MVVM(Model View ViewModel)是一种基于前端开发的架构模式,但是会写前端页面的 Python Web 开发显然也是有必要了解的。它的核心是提供对 View 和 ViewModel 的双向绑定,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,View 的变动,自动反映在 ViewModel 上,反之亦然,这样就保证视图和数据的一致性。

ViewModel 是 Model 和 View 之间的桥梁,它的设计原则是:

  • 为 Model 和 View 提供适配
  • 如果有需要转换的过程,尽可能在 ViewModel 中做,保持 Model 的纯洁,View 的清晰。

现在比较流行的 MVVM 框架是 Vue.js 和 AngularJS。不能不感叹下,前端太能玩了。

我为什么聊 MVVM 呢,其实是我注意到现在有一些现象:

  1. 一些后端兼写前端的同学还在用 jQuery + Template 的方式在开发。
  2. 一些没什么交互的、甚至还是多页面的网站上被用上了 React、Vue 等方案。

当然,黑猫白猫能抓老鼠就是好猫,我说的是没有用对地方。

现在前后端交互通常是如下流程:

  1. 用户要求获取界面上的数据
  2. 通过 API 向后端请求数据,后端计算完成返回
  3. 界面拿到数据进行重新渲染

我们设想一个复杂的场景下,有个页面需要后端的数据来生成界面,通过在界面的某些控件输入或者触发事件的方式重新获取数据变换成其他界面。注意,整个过程页面的 URL 没有发生跳转。

传统的类似 jQuery + Template 的方式有以下痛点:

  1. 开发者在代码中大量调用相同的 DOM API, 处理繁琐,操作冗余,使得代码难以维护。
  2. 大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
  3. 当 Model 频繁发生变化,开发者需要主动更新到 View ;当用户的操作导致 Model 发生变化,开发者同样需要将变化的数据同步到 Model 中, 这样的工作不仅繁琐,而且很难维护复杂多变的数据状态。

其实数据和界面的变更之间,一般都是存在对应关系的。如果能够引入一种绑定关系,经过一系列的配置过程,使得以后每次数据发生变更,界面都会自动跟着作对应变动;界面上的操作,也会自动更新到数据,那开发过程就会非常省事了,绝大部分此类操作都会转化为配置,供绑定框架用来建立数据和界面之间的关联关系。MVVM 框架可以帮助你可以减少大量的 DOM 操作没提高渲染性能。

随着前端组件化、MVVM 框架的发展,后端模板引擎来还前途嘛?

我实际工作中很多内部用到类似后台的项目,大多设计成只用基本模板 index.html,其中引用了由 webpack 编译 react/vue 源码生成的 javascript 文件的方式,后端提供 API。但是并不代表后端模板的没落:

  1. 基于 SEO 的考虑。后台渲染,组装页面,对 SEO 最友好。由于 Python 后端无法用 NodeJS 那样的「Server Rendering」方案(发现是爬虫就服务端渲染,如果是正常请求在客户端渲染,缺点是多了服务端渲染的工作)。所以必须要有必要的数据使用后端模板引擎渲染。所以要注意真正适合做 SPA (单页面应用) 的应用,SEO 没有什么意义,因为登陆才能用,一般只需要让一些市场营销的静态页面能被 SEO 就好了。所以担心的前提是有冲突的。
  2. 某些场景前端无法完成。比如导出文件、秒开(复杂效果的)首屏。
  3. 后端模板对后台开发更友好。Web 开发中的模板部分只是其中一部分事情,职责划分上不可能那么清晰,所以后台开发当然更喜欢在不适合做 SPA 的应用上使用模板来完成和其他部分(比如 Model 和 View 的维护和更新)相关的对接工作。比如 Python 语言的 Mako 模板,简直就是在写 Python。

所以大家还是得好好学 Jinja2、Mako 吧。

参考资料

在开发过程中如何应用 mvvm 思想(非现有的框架)? Vue.js 和 MVVM 的小细节