您和您的同事都没有明确正确,尽管您的问题似乎源于对模式的概念性误解,我将尝试通过这个答案来说明。底线是,视图就是视图,模型就是模型,控制器就是控制器。如果您打算对 MVC 采取极其严格的立场,那么您需要将模式的每个组件视为模式中的有效参与者,而不是通过将 view 组件表示为简单但最终不被重视的成员。 (我在这里专门回应视图只是模型的返回值的概念。)
考虑到这个问题会出现更多的混乱:
编写 WEB API 是否意味着使用 MVC - 如果它要被客户端上的 JS 应用程序使用?
模型-视图-控制器模式侧重于关注点分离;具体来说,它涉及将处理域数据的方式与将其呈现给最终消费者的方式分开。控制器负责两者之间的中介:即通知模型视图中所做的更改,并通知视图模型中所做的更改。 (进一步参见:Martin Fowler 在Patterns of Enterprise Application Architecture 中对该模式的正式描述,第 330 页)
编写 Web API 不会消除 MVC 模式的适用性。 MVC 模式的使用特定于 应用程序 的体系结构,而不是整个 系统 的体系结构。构成您系统的所有应用程序可能使用 MVC 模式,但同样适用的是,一个应用程序使用 MVC 模式,而另一个应用程序从具有表示逻辑的同一层访问数据存储.
明确地说,我在此上下文中将应用程序定义为可以加载到运行时环境中的东西,而不是系统,它是一组相互关联的应用程序,可能会或可能不会在同一运行时执行环境甚至在相同的硬件上。我还应该指出,我将交替使用术语“范式”和“模式”,尽管“架构”这个词是特定于其上下文的。
一个例子
作为一个例子,假设我正在制作一个允许用户创建和管理博客文章的系统。为此,我将编写两个应用程序:一个在公开 RESTful Web API 的服务器上执行,另一个在服务器上托管但在用户计算机上的用户浏览器中执行作为单页应用程序 (SPA)。 RESTful Web API 和 SPA 都将使用模型-视图-控制器架构组成,因此我将以这些形式来介绍它们。
在 Web API 应用程序中,我的 model 层表示域模型(在本例中为博客文章和用户)。我还通过使用对象关系映射库让我的模型层了解域模型如何映射到我的关系数据库。我的模型层负责从数据存储(即我的数据库)中持久化和检索对象,并处理与这些对象相关的必要领域逻辑(例如,如果帖子标题太长,则抛出错误)。
在 Web API 应用程序中,我有我的 controller 层,它接受传入的 HTTP 请求并将它们连接到作为方法实现的控制器层中的各个控制器。一个控制器可能会接受针对特定 API 端点的传入 HTTP POST 请求,其中 JSON 有效负载表示新的博客文章。控制器反序列化该有效负载并将原始数据传递给模型层以进行验证和持久化(即写入数据库)。模型层必须通知控制器层它是否可以成功验证和持久化它提供的数据。然后将该结果发送到视图层以最终呈现给消费者。
在 Web API 应用程序中,我的 view 层负责获取控制器操作的结果并以某种有意义的方式将其返回给 API 使用者。在这种情况下,我的视图层负责向消费者返回某种类型的 HTTP 响应。如果上面段落中描述的控制器将模型层无法成功验证数据以及无法验证的原因传递给视图层,则视图层负责制作描述错误的有效负载(并且可能用于在 HTTP 响应上设置适当的 HTTP 状态代码,尽管这可能是处理将视图返回给消费者的更高服务层的责任)。另一方面,被调用的控制器可能被要求检索某个博客文章。控制器将从模型层接收到的数据传递给视图层。视图层将从控制器接收到的数据转换为 JSON 负载,形成响应的主体。
在 SPA 中,我有一个 model 层,它代表我的域模型(同样,博客文章和用户)。我还通过使用 RESTful AJAX 请求库让我的模型层了解域模型如何映射到我的 Web API。我的模型层负责从数据存储(即我的 Web API)中持久化和检索对象并处理与这些对象相关的必要域逻辑(例如,如果帖子标题太长)。
在 SPA 中,我有我的 controller 层,该层负责通过作为方法实现的各个控制器通知模型层和视图层更改来响应用户输入。一个控制器可以通过通知视图层使用博客文章创建表单更新自己来处理用户导航到博客文章创建屏幕。另一个控制器可能会处理用户保存该帖子的请求,在这种情况下,控制器会将博客帖子数据传递给模型层以进行验证和持久化(即发送到 Web API)。模型层必须通知控制器层它是否可以成功验证和持久化它提供的数据。然后将该结果发送到视图层进行最终呈现。
在 SPA 中,我有我的 view 层,它负责获取控制器操作的结果并对 HTML 模板文件执行转换,然后将其显示在浏览器窗口中。某些控制器操作可能不需要将任何数据嵌入到模板中;也就是说,HTML 模板文件直接呈现给用户,无需更改。同样,控制器操作可能会通过替换文件中的某些属性来传递需要嵌入到模板中的数据。模板文件还可能包含某些 UI 元素,它们调用控制器层中的控制器来激活。
这个例子旨在说明三件事:
模型层负责领域模型和领域模型的业务逻辑。它甚至不一定负责持久化和检索此类模型,因为这可能超出了软件的规范。如果软件负责持久化和检索此类模型,则模型层负责与持久层通信。持久层处理将数据保存到应用程序外部的位置(例如,关系数据库、Exchange 服务器、NoSQL 数据库、文件系统、Web API、SOAP 服务、核心数据存储、主引导记录、我的冰箱、国际空间站等)。模型层实际上并不关心存储数据的内容或存储位置。它只知道数据是什么样子以及可以用它做什么。
控制器层负责接受消费者输入,并根据输入的需要在模型层和视图层之间进行调解。控制器层不负责执行业务逻辑;它不负责使用外部存储检索或保存数据;它不负责渲染视图。一个单独的服务层可以处理向控制器层发送输入,这取决于系统的架构。
视图层负责以消费者期望的某种方式呈现来自控制器层的任何数据。这可能意味着重绘 UI 或向消费者发送 HTTP 响应。控制器可以传递某些数据,告知视图层消费者想要什么;例如,一个 Web API 消费者可能会设置一个 HTTP 头 Accept: text/plain,视图层的工作是对控制器传入的原始数据进行适当的转换,以满足消费者的请求。它不负责与模型层交互(视图层需要的任何数据都必须由控制器传入)。它只负责将控制器传入的数据转换为消费者期望的格式,并将格式化的数据呈现给消费者。一个单独的服务层可以处理视图的实际呈现,这取决于系统的架构。
你、你的同事和模型-视图-控制器模式
既然我已经建立了这个基础,我将回到您最初的问题,并检查为什么您和您的同事都不正确。之后,我将讨论 MVC 与 Model 2 的关系。
让我们从你的同事关于视图及其与模型的关系的假设开始:
他认为来自模型的东西就是视图......无论是 JSON、EDI、HTML 等。它是“最终消费者”消费的东西,无论是浏览器还是其他系统......
如上所示,模型和视图是应用程序的两个独立层。该模型不返回 JSON、EDI 或 HTML 或任何其他类型的格式化数据。该模型返回的封装数据仅在其运行时环境中对应用程序有意义,因为只有存储在临时内存中的字节代表了控制器可以使用的实际数据的模型。简单来说,模型返回的数据并不意味着最终消费者或最终用户的最终消费。通常,对于使用 MVC 架构的面向对象语言构建的应用程序,模型层将返回一个类的实例,即,一个对象,而不是 EDI 或 HTML。 (基于 JavaScript 的模型层可能会返回有效的 JSON,但这是因为 JSON 是 JavaScript 的子集,而不是将数据格式化为 JSON 的模型)。
你的同事是正确的 但是,因为视图 是 JSON、EDI、HTML 或任何 view 层返回,包括 UI 重绘。他的问题是认为这些表示是由模型层生成的,而实际上它们是由视图层根据从模型传递到控制器的数据生成的到视图。 (视图甚至不应该知道模型层。)
我认为 UI 是视图,在我们的例子中是 HTML……或者它可能是 FLASH 或其他一些 UI。但是,传输到另一个系统或浏览器的 JSON 或其他格式的数据不是 MVC 中的 VIEW,而是刚刚传输和使用的数据包,很可能由另一个控制器......然后被传递给 VIEW,或者不同的模型。
仅当 UI 作为视图层的组件而不是作为另一个应用程序的组件生成时,UI 才是视图。例如,如果您有一个 Ruby on Rails 应用程序从视图层呈现 HTML 页面,那么是的,生成的 HTML 就是视图。但 Adobe Flash 应用程序不是视图(除非您的视图层动态构建 Flash 应用程序,然后将其返回给消费者)。 Adobe Flash 应用程序不属于原始应用程序的范围,它本身就是一个应用程序。因此,它不是原始应用程序的 MVC 架构的一部分。但是,Adobe Flash 应用程序可以在内部具有 MVC 架构。
JSON 内容可以是一个视图,只要是视图层输出的即可。此内容是被传输到另一个应用程序或系统还是被另一个应用程序的控制器使用并不重要。
服务器(模型 - 业务逻辑)
CONTROLLER(服务器端脚本,或客户端上的 javascript - 这些天似乎角色分开了)
VIEW(浏览器,网页、矢量或其他一些 UI)
我认为控制器在服务器和客户端之间划分是否正确 - 特别是在网络应用程序世界中?或者这只是服务器上的 MVC 架构与客户端上的 MVC 分开的一种情况......因为有 JS MVC 框架......
你这样想是不对的。 的情况是,在服务器上执行的应用程序中的 MVC 架构与在客户端上执行的应用程序中的 MVC 架构是分开的。您试图描述模型-视图-控制器范式,就好像它应用于系统。这是对模型-视图-控制器范式的误解。
MVC 范例是在应用程序 的上下文中实现的。您在问题中描述的是多层架构。这可以缩减为客户端-服务器架构,其中只有服务器(处理域建模、业务逻辑和请求处理)和客户端(例如,显示来自服务器的 HTML 结果的 Web 浏览器) 存在。这取决于您查看系统架构的范围。不过,它绝不是模型-视图-控制器架构。
如上所述,应用程序实现了模型-视图-控制器模式。这些应用程序可以组合成一个多层系统架构,例如我给出的Web API和SPA的例子。两个应用程序(Web API 和 SPA)都实现了 MVC 模式。结合起来,它们代表了多层系统的一部分。但是,它们构成的系统不能具有 MVC 架构。
我认为需要指出的一件事是,MVC 模式不仅仅存在于 Web 技术中,因此它恰好包含在 JavaScript 框架中。 MVC 模式是一种广泛适用的模式,它通过分离代码库区域的关注点来简化代码库维护。 (也就是说,我可以说 this 代码不应该有任何特定于视图的逻辑,因为它只关心领域模型,而 那个 代码不应该有任何关于域模型是如何工作的,因为它只关心视图是如何呈现的。)理论上,M 部分可以从我的应用程序中提取出来并编写为一个库,该库被称为依赖项多个应用程序。一个可能是 Web 服务,另一个可能是 GUI 应用程序,但两者都使用相同的域模型代码,因为 MVC 模式提供的抽象级别允许如此简单的重用。
模型 2 和 MVC
关于您对Model 2 和模型-视图-控制器范式的争论,模型 2 与 MVC 模式不一致。首先要意识到,“Model 2”中的“Model”一词与“Model-View-Controller”中所指的领域模型无关。在这种情况下,这个词实际上是“示例”的同义词,应该这样认为。它最初显示为an example of how to use JavaServer Pages。这种模式最终成为了一种使用范式。
模型 2 仅指定动态内容生成应发生在最终视图生成器中该内容的实际构造之外,并且最终视图生成器不应包含业务逻辑。具体来说,Java Servlet 接受传入的请求,对其进行解析,执行任何必要的数据存储通信,然后生成包含任何动态数据的 Java Bean,这些动态数据将被格式化以呈现给消费者。然后,JavaServer Page 接管并访问生成的 Java Bean 以编译发送回消费者的演示文稿。
因此可以认为模型 2 范式实现了 MVC 模式的“控制器”组件,因为 Java Servlet 负责接收和解析请求。也可以考虑实现 MVC 模式的“视图”组件,因为 JavaServer Page 负责呈现视图。然而,模型 2 没有明确的“模型”组件。在原始示例中,Java Servlet 直接访问数据存储,违反了 MVC 强制执行的关注点分离控制。模型 2 仅将生成视图的关注点分离出来,而将其他所有内容留给 Java Servlet。所以 Model 2 作为一种模式,并不直接与 MVC 并行存在。
这并不意味着 Model 2 应用程序不能同时是 MVC 应用程序。如果软件架构师将模型层添加到具有模型 2 架构的应用程序中,并且该模型层仅从 Java Servlet 调用,那么该架构也隐含地满足 MVC 模式的标准。两者可以很容易地共存。模型 2 恰好具有有限的适用范围:即,适用于使用 Java Servlet 和 JavaServer Pages 的应用程序。