ChatGPT解决这个技术问题 Extra ChatGPT

什么进入“MVC”中的“控制器”?

我想我了解 MVC 的基本概念 - 模型包含应用程序的数据和行为,视图负责向用户显示它,控制器处理用户输入。我不确定的正是控制器中的内容。

例如,我有一个相当简单的应用程序(我特别考虑 Java,但我想同样的原则也适用于其他地方)。我将我的代码组织成 3 个包,分别称为 app.modelapp.viewapp.controller

app.model 包中,我有一些反映应用程序实际行为的类。这些 extends Observable 并使用 setChanged()notifyObservers() 在适当的时候触发视图更新。

app.view 包有一个类(或多个类用于不同类型的显示),它使用 javax.swing 组件来处理显示。其中一些组件需要反馈到模型中。如果我理解正确,视图不应该与反馈有任何关系——应该由控制器处理。

那么我实际上在控制器中放入了什么?我是否只需调用控制器中的方法就将 public void actionPerformed(ActionEvent e) 放入视图中?如果是这样,是否应该在控制器中进行任何验证等?如果是这样,我如何将错误消息反馈回视图 - 应该再次通过模型,还是控制器应该直接将其发送回视图?

如果验证在 View 中完成,我在 Controller 中放置什么?

很抱歉这个问题很长,我只是想记录一下我对这个过程的理解,希望有人可以为我澄清这个问题!


A
Andres Jaan Tack

在您建议的示例中,您是对的:界面中的“用户单击了'删除此项目'按钮”基本上应该只是调用控制器的“删除”功能。然而,控制器不知道视图是什么样的,因此您的视图必须收集一些信息,例如“单击了哪个项目?”

在对话形式中:

视图:“嘿,控制器,用户刚刚告诉我他想删除第 4 项。”管制员:“嗯,在检查了他的凭据后,他可以这样做了……嘿,模特,我希望你得到第 4 项,然后做任何你想做的事情来删除它。”模型:“第 4 项……知道了。它被删除了。还给你,管制员。”控制器:“这里,我会收集新的数据集。返回给你,查看。”视图:“酷,我现在将新的集合展示给用户。”

在该部分的末尾,您有一个选择:视图可以发出单独的请求,“给我最新的数据集”,从而更加纯粹,或者控制器隐式返回新的数据集,并带有“删除“ 手术。


该对话是我遇到的对 MVC 的最佳解释,谢谢!
一切都很好,但是直接从模型中读取的视图没有任何问题。 “控制者不是数据警察”。还有一种学说说要让控制器保持苗条。 View Helpers 是收集数据以供您的视图使用的理想场所。不必为了重用某些数据访问逻辑而调度完整的控制器堆栈。更多详情:rmauger.co.uk/2009/03/…
我同意“例外 e”。模型中的数据可以由许多事件更新,不一定是控制器,因此在一些 MVC 设计中,M 向 V 发出信号,表明数据是脏的,V 可以自行刷新。在这种情况下,C 没有任何作用。
控制器看起来像 DDD 术语中的应用程序服务,因为在对话框中它管理一些类似 Saga 的场景,可能在事务中。
这看起来就像控制器充当模型层和视图之间的“中间人”。但这听起来像 MVP,而不是 MVC(第三个元素是“演示者”,我认为它通常被描绘为中间人)。对我来说,在 MVC 中,您似乎只将控制器发送到模型,视图也从该模型观察。
B
Bert F

MVC 的问题在于人们认为视图、控制器和模型必须尽可能相互独立。他们没有 - 视图和控制器经常交织在一起 - 将其视为 M(VC)

控制器是用户界面的输入机制,它经常与视图纠缠在一起,尤其是与 GUI。然而,视图是输出,控制器是输入。视图通常可以在没有相应控制器的情况下工作,但控制器通常在没有视图的情况下就没那么有用了。用户友好的控制器使用视图以更有意义、更直观的方式解释用户的输入。这就是很难将控制器概念与视图分开的原因。

将一个无线电控制的机器人想象成一个密封盒子中的探测场作为模型。

该模型是关于状态和状态转换的,没有输出(显示)或触发状态转换的概念。我可以得到机器人在场上的位置,并且机器人知道如何转换位置(向前/向后/向左/向右走一步。在没有视图或控制器的情况下很容易想象,但没有任何用处

想象一个没有控制器的视图,例如,在网络上的另一个房间里,另一个房间里的某个人正在观察机器人的位置,因为 (x,y) 坐标沿着滚动控制台向下流动。这个视图只是显示模型的状态,但这家伙没有控制器。同样,很容易在没有控制器的情况下想象这个视图。

想象一个没有视图的控制器,例如有人锁在壁橱里,无线电控制器调谐到机器人的频率。该控制器正在发送输入并导致状态转换,但不知道它们对模型做了什么(如果有的话)。很容易想象,但如果没有来自视图的某种反馈,它并没有真正有用。

大多数用户友好的 UI 将视图与控制器协调以提供更直观的用户界面。例如,想象一个带有触摸屏的视图/控制器,以二维显示机器人的当前位置,并允许用户触摸屏幕上恰好位于机器人前面的点。控制器需要有关视图的详细信息,例如视口的位置和比例,以及触摸点相对于屏幕上机器人像素位置的像素位置)才能正确解释这一点(不像那个被锁在壁橱里的人)无线电控制器)。

我已经回答你的问题了吗? :-)

控制器是从用户那里获取用于使模型转换状态的输入的任何东西。尝试将视图和控制器保持分离,但要意识到它们通常是相互依赖的,所以如果它们之间的边界模糊也可以,即视图和控制器作为单独的包可能不会像你想的那样完全分离喜欢,但没关系。您可能必须接受控制器不会与视图完全分离,因为视图来自模型。

...是否应该在控制器中进行任何验证等?如果是这样,我如何将错误消息反馈回视图 - 应该再次通过模型,还是控制器应该直接将其发送回视图?如果验证在 View 中完成,我在 Controller 中放置什么?

我说链接的视图和控制器应该可以自由交互而无需通过模型。控制器接受用户的输入并应该进行验证(可能使用来自模型和/或视图的信息),但如果验证失败,控制器应该能够直接更新其相关视图(例如错误消息)。

对此的严峻考验是问自己一个独立的视图(即另一个房间里的人通过网络观察机器人位置)是否应该因为其他人的验证错误而看到任何东西(例如壁橱里的那个人)试图告诉机器人离开场地)。一般来说,答案是否定的——验证错误阻止了状态转换。如果没有状态转换(机器人没有移动),则不需要告诉其他视图。壁橱里的人只是没有得到任何反馈,表明他试图导致非法转换(没有视图 - 糟糕的用户界面),其他人也不需要知道这一点。

如果那个拿着触摸屏的人试图把机器人送出场地,他会收到一条友好的用户友好信息,要求他不要通过将机器人送出检测场地来杀死机器人,但同样,没有其他人需要知道这一点。

如果其他视图确实需要了解这些错误,那么您实际上是在说来自用户的输入和任何由此产生的错误都是模型的一部分,整个事情有点复杂......


J
Jan Bodnar

MVC 模式只希望您将表示(= 视图)与业务逻辑(= 模型)分开。控制器部分只是为了引起混乱。


正是,我一直想着直到现在却一直没有勇气告诉任何人......或者可能无法想出合适的词。
模型-视图-混淆
J
Jon

这是关于 MVC 基础知识的good article

它指出 ...

控制器 - 控制器将与视图的交互转换为模型要执行的操作。

换句话说,你的业务逻辑。控制器响应用户在视图中执行的操作并做出响应。如果验证失败或成功(错误页面、消息框等),您将验证放在这里并选择适当的视图。

还有一个不错的article at Fowler


MVP 是您参考的文章中讨论的另一个选项,请参阅 martinfowler.com/eaaDev/ModelViewPresenter.html
感谢您的链接,它们肯定会带来有趣的阅读。
C
Community

实际上,我从来没有发现控制器概念是一个特别有用的概念。我在代码中使用严格的模型/视图分离,但没有明确定义的控制器。这似乎是一个不必要的抽象。

就个人而言,成熟的 MVC 似乎是工厂设计模式,因为它很容易导致设计混乱和过于复杂。不要成为architecture astronaut


d
duffymo

控制器实际上是视图的一部分。它的工作是确定需要哪些服务来满足请求,将视图中的值解组为服务接口所需的对象,确定下一个视图,并将响应编组回下一个视图可以使用的形式.它还处理任何抛出的异常并将它们呈现到用户可以理解的视图中。

服务层是了解用例、工作单元和模型对象的东西。每种视图类型的控制器都不同——桌面、基于浏览器、Flex 或移动 UI 的控制器不会相同。所以我说它确实是 UI 的一部分。

面向服务:这就是工作完成的地方。


a
akinuri

根据你的问题,我觉得你对模特的角色有点模糊。模型专注于与应用程序关联的数据;如果应用程序有一个数据库,模型的工作就是与它交谈。它还将处理与该数据相关的任何简单逻辑;如果您有一条规则说明所有情况下 TABLE.foo == "万岁!"和 TABLE.bar == "Huzzah!"然后设置 TABLE.field="W00t!",然后你希望模型来处理它。

控制器应该处理应用程序的大部分行为。所以回答你的问题:

我是否只需调用控制器中的方法就将 public void actionPerformed(ActionEvent e) 放在视图中?

我会说不。我会说它应该存在于控制器中;视图应该简单地将来自用户界面的数据提供给控制器,并让控制器决定应该调用哪些方法作为响应。

如果是这样,是否应该在控制器中进行任何验证等?

您的大部分验证确实应该由控制器完成;它应该回答数据是否有效的问题,如果不是,则将适当的错误消息提供给视图。在实践中,您可以在 View 层中加入一些简单的健全性检查,以改善用户体验。 (我主要考虑 Web 环境,您可能希望在用户点击“提交”时弹出一条错误消息,而不是等待整个提交 -> 进程 -> 加载页面循环,然后再告诉他们他们搞砸了.) 小心点;您不希望重复工作,而且在很多环境中(再次,我想到的是网络),您经常不得不将来自用户界面的任何数据视为一堆肮脏的肮脏直到你确认它实际上是合法的。

如果是这样,我如何将错误消息反馈回视图 - 应该再次通过模型,还是控制器应该直接将其发送回视图?

您应该设置一些协议,其中视图不一定知道接下来会发生什么,直到控制器告诉它。用户敲击该按钮后,您会向他们显示什么屏幕?视图可能不知道,控制器也可能不知道,直到它查看刚刚获得的数据。它可能是“按预期转到此其他屏幕”或“留在此屏幕上,并显示此错误消息”。

根据我的经验,Model 和 View 之间的直接通信应该非常非常有限,并且 View 不应该直接改变 Model 的任何数据;那应该是控制器的工作。

如果验证在 View 中完成,我在 Controller 中放置什么?

看上面;真正的验证应该在控制器中。并且希望您现在对应该在 Controller 中放置的内容有所了解。 :-)

值得注意的是,边缘都会变得有些模糊。与大多数像软件工程一样复杂的事情一样,判断电话将比比皆是。只需使用您的最佳判断,尝试在此应用程序中保持一致,并尝试将您学到的课程应用到下一个项目中。


s
staticsan

这是我使用的经验法则:如果它是我将专门用于此页面上的操作的过程,它属于控制器,而不是模型。该模型应该只为数据存储提供一个连贯的抽象。

我是在使用开发人员编写的大型 web 应用程序后提出的,这些开发人员认为他们了解 MVC,但实际上并没有。他们的“控制器”减少到 8 行调用静态类方法的行,这些方法通常在其他任何地方都没有调用:-/ 使他们的模型只不过是创建命名空间的方法。正确地重构这三件事:将所有 SQL 转移到数据访问层(又名模型),使控制器代码更冗长但更易于理解,并将旧的“模型”文件减少到零。 :-)


J
Jon

控制器主要用于视图和模型之间的协调。

不幸的是,它有时最终会与视图混合在一起——在小型应用程序中,虽然这还不错。

我建议你把:

public void actionPerformed(ActionEvent e)

在控制器中。然后你的视图中的动作监听器应该委托给控制器。

至于validation部分,可以放在view里面或者controller里面,我个人觉得是属于controller里面的。

我绝对会推荐看看 Passive View and Supervising Presenter (这基本上是 Model View Presenter 被分成的部分——至少 Fowler 是这样)。看:

http://www.martinfowler.com/eaaDev/PassiveScreen.html

http://www.martinfowler.com/eaaDev/SupervisingPresenter.html


a
akf

还要注意,每个 Swing 小部件可以被认为包含三个 MVC 组件:每个都有一个模型(即 ButtonModel)、一个视图(BasicButtonUI)和一个控件(JButton 本身)。


m
mnuzzo

您在控制器中放入的内容基本上是正确的。这是模型应该与视图交互的唯一方式。执行的动作可以放在视图中,但实际功能可以放在另一个类中,该类将充当控制器。如果您要这样做,我建议您研究命令模式,这是一种抽象具有相同接收器的所有命令的方法。抱歉跑题了。

无论如何,正确的 MVC 实现将仅具有以下交互:模型 -> 视图视图 -> 控制器控制器 -> 视图

唯一可能存在其他交互的地方是,如果您使用观察者来更新 View,那么 View 将需要向 Controller 询问它需要的信息。


D
David Seiler

据我了解,控制器从用户界面操作转换为应用程序级操作。例如,在视频游戏中,控制器可能会将“将鼠标移动了这么多像素”翻译成“想要朝某个方向看”。在 CRUD 应用程序中,翻译可能是“点击某某按钮”到“打印这个东西”,但概念是一样的。


F
FYA

我们就是这样做的,主要使用控制器来处理和响应用户驱动的输入/操作(以及 _Logic 用于其他所有内容,除了视图、数据和明显的 _Model 内容):

(1) (response, response - webapp对用户的响应"做什么") Blog_Controller

->主()

->handleSubmit_AddNewCustomer()

->verifyUser_HasProperAuth()

(2)(“业务”逻辑,webapp“思考”的内容和方式)Blog_Logic

->sanityCheck_AddNewCustomer()

->句柄用户名更改()

->sendEmail_NotifyRequestedUpdate()

(3)(视图、门户、webapp 如何“出现”)Blog_View

->genWelcome()

->genForm_AddNewBlogEntry()

->genPage_DataEntryForm()

(4)(仅数据对象,在每个 Blog* 类的 _construct() 中获取,用于将所有 webapp/inmemory 数据作为一个对象保存在一起)Blog_Meta

(5)(基础数据层,读/写数据库)Blog_Model

->saveDataToMemcache()

->saveDataToMongo()

->saveDataToSql()

->加载数据()

有时我们对将方法放在哪里有点困惑,在 C 或 L 中。但模型坚如磐石,晶莹剔透,而且由于所有内存中的数据都驻留在 _Meta 中,所以在那里也很容易.顺便说一句,我们最大的飞跃是采用 _Meta 使用,因为这从各种 _C、_L 和 _Model 对象中清除了所有的杂物,使得这一切在精神上都易于管理,而且,它一举为我们提供了称为“依赖注入”,或一种将整个环境与所有数据一起传递的方法(其好处是轻松创建“测试”环境)。