具有封装/防御性编程的 MVC(模型视图控制器)

Posted

技术标签:

【中文标题】具有封装/防御性编程的 MVC(模型视图控制器)【英文标题】:MVC (Model View Controller) with Encapsulation/Defensive Programming 【发布时间】:2019-04-06 00:35:44 【问题描述】:

我学习了 MVC (model-view-controller),但我一直无法弄清楚如何以尊重三个组件之间封装的方式实现它。

例如,我总是被教导前端和后端应该完全分开,并且不应该交互。但在 MVC 中,Model 实际上是对视图进行变异,这意味着模型对视图有引用。

同样,控制器需要接收来自前端的输入(例如按钮、文本框等)并改变模型。控制器如何既与视图分离,又如何在视图内部拥有其组件(按钮、文本框)(否则它们不会显示给用户)。这是否意味着视图必须是可变的?

我问过我的教授,并指出了 MVC 在抽象和封装方面的一些违规行为,他回答说“这是设计的一部分,负责任地使用对视图和模型的引用。如果有人设计模型恰巧利用了视图的不封装,那么就违反了设计的约定。”

有没有一种方法可以实现 MVC,使得任何一个组件都不会对另一个组件造成伤害(例如,模型不能删除视图场景,控制器不能清空模型内的所有数据等)。关键是在视图和模型中设置许多访问器/修改器吗?

【问题讨论】:

OP,您是在考虑 Web 应用程序的 MVC 还是更一般意义上的 MVC? 【参考方案1】:

当我实现 MVC 时,模型既不能访问控制器,也不能访问视图。事实上,我试图将模型限制为简单的原始数据——比如数据库中记录的副本。简单、轻便。

控制器是模型的包装器,是所有重型逻辑所在的地方。它独自负责修改底层模型(如果可编辑),执行相应的业务逻辑,并在事情发生变化时引发事件。 Controller 不能直接访问 View(可能有多个):View 调用 Controller 函数并更新它的属性。控制器还可以将原始模型数据“按摩”成一种更易于 UI 层交互的格式。

视图与模型没有直接交互。它更新以响应来自控制器的事件。用户交互(如编辑文本框、单击按钮等)通常会重定向到控制器。 UI 层应该只关心 UI 特定的工作。

这样你就有了一个很好的分层软件架构:UI -> 业务逻辑 -> 数据。没有任何东西可以直接与它上面的层交互——只允许通过事件进行间接交互。您还可以将同一个 Controller 实例传递给多个 View,以便它们保持同步。

【讨论】:

这不是 MVC。请参阅 Wikipedia,或来自@Kata 的答案。 @jaco0646 - 这是错误的。模型可以只是 MVC 中的纯数据。您还可以给他们一些有限的行为(如数据更改事件) - 但大部分逻辑都进入控制器。正如***所述: 1. 模型负责管理应用程序的数据。它接收来自控制器的用户输入。 2. 视图意味着以特定格式呈现模型。 3. 控制器响应用户输入并对数据模型对象进行交互。控制器接收输入,可选地对其进行验证,然后将输入传递给模型 此外,模型会在其数据更改时更新视图。控制器不会更新模型然后更新视图。如图所示,流程绕了一圈。 是的,数据更改事件通常会进入数据层,这将是与我上面所做的一个差异。 View 实际上并不关心事件来自哪里,但是,通过事件更新 View 很重要。由于控制器负责修改数据 - 而不是视图 - 控制器触发此类事件是合理的,因为它总是知道并启动此类更改。此外,MVC 指的是一系列编程模型,其中有很多这样的变体(MVP、MVVM 等),您确实可以在其中看到控制器触发事件​​。 很高兴听到这个消息!实际上,这些天我不介意过多地关注这些术语。只需设计您的程序,使其易于管理和重用,就是这样!因此,如果您想将业务逻辑放入 Controller 中,请继续。但在未来,如果您想在另一个不需要 UI 或需要不同 UI 的环境中重用该逻辑,那么您应该将该逻辑提取到 Model 中。【参考方案2】:

模型对象不是哑数据,它们是具有行为的真实对象。所以控制器不能像你说的那样清空模型的数据。 Controller 的工作只是将 View 发送的输入转换为 Model 暴露的行为可以理解的格式。如果转换成功,Controller 会将转换后的输入传递给 Model。否则Controller会直接通知View而不打扰Model。

模型也不能像你说的那样直接改变视图。 Model 保持对 View 的间接引用:Model 不依赖于 View 类型,而是依赖于 View 实现的接口。模型只是注意到视图的输出。 View 如何响应(如删除场景)接收到的输出取决于 View。

让对象直接改变其他对象的数据在 OOP 中是一种非常糟糕的做法。

【讨论】:

感谢您的回复。你能举一个简单的例子来说明"Model keeps an indirect reference to View" 的含义吗? class Model private OutputPort op; public Model(OutputPort op) this.op = op; ... interface OutputPort void setResult(String result); ... class View implements OutputPort public void setResult(String result) this.resultLabel.setText(result); ... 你可能会觉得这篇文章很有趣blog.cleancoder.com/uncle-bob/2012/08/13/… @Hatefiend,模型和视图的关系是Observer Pattern。 模型不应显式引用视图。视图应该通过事件通知模型的更新。事件源可以是模型本身,也可以是控制器。【参考方案3】:

将其视为两个独立的应用程序:

控制器存在于一个应用程序中。它们的目的是接受输入和/或创建输出。输入和输出都是一个模型,通常是同一个模型(经过一些验证)。模型只是输入和输出的愚蠢容器。

您可以完全独立地对控制器进行单元测试。测试工具可以只为它们提供输入(并模拟它们的依赖项)并检查它们的输出。因为模型很笨,他们甚至不必被嘲笑。

视图存在于另一个应用程序中。他们的目的是接受输入并使页面看起来以某种方式。输入当然是模型。

在干净的 MVC 架构中,模型与 DTO 一样愚蠢。模型不操纵视图或操纵控制器;事实上,他们完全没有任何行为也很好。当你这样做时,View 和 Controller 之间完全没有依赖关系,Model 只是充当一个接口。这就是您获得封装和关注点分离的方式。

【讨论】:

这个答案是完全错误的。在 MVC 中,“模型是模式的核心组件。”Wikipedia 这个答案是完全错误的。模型是关于业务领域的,包括数据/逻辑/行为或属于该领域的任何内容。控制器和视图是非域事物:UI。 恕我直言,@jaco0646 和 kata,鉴于 MVC 的复杂性和历史,您的反馈太非黑即白了。现代 MVC 实际上是对 Model2 模式的改编,在这种情况下,模型服务于之前分配给 ViewModel 的角色。业务逻辑被提取到注入控制器的服务中,并且可能有自己的“模型”(域对象),这就是我怀疑您所指的。但实际上也有一些争论。如果我稍后有时间,将编辑我的帖子并附上参考资料。 我说的是由 Trygve Reenskaug 创建的原始 MVC,然后 Martin Fowler 在他的 book(第 14 章)和 Bob 叔叔在他的 article 中回应了它。如果这个问题的提问者澄清他没有提到原始 MVC,那么我将删除我的评论。

以上是关于具有封装/防御性编程的 MVC(模型视图控制器)的主要内容,如果未能解决你的问题,请参考以下文章

iOS设计模式初识-02

初识Spring框架

初识Spring框架

什么是 MVC(模型视图控制器)? [关闭]

创建与主站点具有相同模型和控制器的 MVC 移动站点

Backbone学习笔记