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


186

我想我了解MVC的基本概念-模型包含应用程序的数据和行为,视图负责将其显示给用户,控制器负责处理用户输入。我不确定控制器中到底是什么

比方说,我有一个相当简单的应用程序(我在专门考虑Java,但我想相同的原理也适用于其他地方)。我整理我的代码放入3包叫app.modelapp.viewapp.controller

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

app.view封装具有一个使用类(或多个类为不同类型的显示器)javax.swing组件来处理的显示。其中一些组件需要反馈到模型中。如果我理解正确,则View应该与反馈没有任何关系-应该由Controller处理。

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

如果验证是在View中完成的,我该在Controller中输入什么?

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

Answers:


519

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

以对话形式:

查看:“嘿,控制器,用户只是告诉我他要删除项目4。”
财务主任:“嗯,检查完他的证书后,他就可以这样做了……嘿,模特儿,我希望你得到第4项,并尽一切努力将其删除。”
型号:“项目4 ...收到。它已被删除。回到您的位置,控制器。”
控制器:“在这里,我将收集新的数据集。请回到视图中查看。”
视图:“很酷,我现在将新设置显示给用户。”

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


90
对话是我遇到的MVC的最好解释,谢谢!
Paul Walker

13
一切都很好,但是直接从模型中读取视图没有错。“控制器不是数据警察”。还有一种说可以使控制器变薄的原则。View Helpers是收集可供视图使用的数据的理想场所。不必分配完整的控制器堆栈来重用某些数据访问逻辑。更多详细信息:rmauger.co.uk/2009/03/…– 2009
例外

1
我同意“例外e”。模型中的数据可以通过许多事件(不一定是控制器)来更新,因此在某些MVC设计中,M向V发出信号,告知V数据脏了,并且V可以刷新自身。在这种情况下,C不起作用。
Mishax 2014年

68

问题MVC在于人们认为视图,控制器和模型必须彼此尽可能独立。他们不是-视图和控制器经常交织-认为是M(VC)

控制器是用户界面的输入机制,通常在视图中比较复杂,尤其是对于GUI。不过,视图是输出,而控制器是输入。视图通常可以在没有相应控制器的情况下工作,但是没有视图的控制器通常远没有那么有用。用户友好的控制器使用该视图以更有意义,更直观的方式解释用户的输入。因此,很难将控制器概念与视图区分开。

可以将密封盒中检测场上的无线电遥控机器人视为模型。

该模型只涉及状态和状态转换,没有输出(显示)或触发状态转换的概念。我可以在现场获得机器人的位置,并且机器人知道如何转换位置(向前/向后/向左/向右迈出一步。很容易在没有视图或控制器的情况下进行构想,但无济于事

考虑一个没有控制器的视图,例如,网络中另一个房间中的某个人在另一个房间中观察机器人位置,因为(x,y)坐标沿滚动控制台向下流。该视图仅显示模型的状态,但此人没有控制器。同样,无需控制器即可轻松实现此视图。

考虑一个没有视线的控制器,例如有人将壁橱锁在壁橱中,而无线电控制器已调整到机器人的频率。该控制器正在发送输入,并导致状态转换,但不知道它们对模型做了什么(如果有的话)。易于设想,但没有视图的某种反馈就没有真正的用处。

大多数用户友好的UI都会与控制器协调视图,以提供更直观的用户界面。例如,想象一下带有触摸屏的视图/控制器,该视图/控制器以二维方式显示机器人的当前位置,并允许用户触摸屏幕上恰好在机器人前面的点。控制器需要有关视图的详细信息,例如视口的位置和比例,以及相对于机器人在屏幕上的像素位置所触摸的点的像素位置),以正确地进行解释(与锁定在壁橱中的家伙不同)无线电控制器)。

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

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

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

如果验证是在View中完成的,那么我应该在Controller中输入什么?

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

为此,要进行严格的测试,以问问自己是否由于他人的验证错误(例如,壁橱中的那个人)而使独立视图(即,另一个房间中的那个人通过网络监视机器人的位置)是否应该看到任何东西。试图告诉机器人下地步)。通常,答案是否定的-验证错误阻止了状态转换。如果没有状态转变(机器人没有移动),则无需告诉其他视图。壁橱里的那个家伙只是没有得到任何试图导致非法转换的反馈(没有视图-用户界面不好),其他人也不需要知道。

如果带有触摸屏的人试图将机器人从现场送出,他会收到一条友好的用户友好消息,要求他不要通过将其从检测场上送出而杀死机器人,但是同样,没有其他人需要知道这一点。

如果其他视图确实需要了解这些错误,那么您实际上是在说用户的输入和任何由此产生的错误是该模型的一部分,整个过程要稍微复杂一点……


23

这是一篇有关MVC基础知识的好文章

它指出 ...

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

换句话说,您的业务逻辑。控制器响应用户在视图中采取的动作并做出响应。您可以在此处放置验证,如果验证失败或成功(错误页面,消息框等),则选择适当的视图。

福勒还有一篇好文章


MVP是您参考的文章中讨论的另一个选项,请参阅martinfowler.com/eaaDev/ModelViewPresenter.html
乔恩

感谢您提供的链接,它们肯定使您阅读有趣。
Paul Walker

18

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


1
确实,直到现在我一直在想,但是从来没有勇气告诉任何人.....或者可能无法说出正确的话。
user1451111 '18

1
模型-视图-混乱
下雨

10

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

就个人而言,成熟的MVC似乎就像工厂设计模式,因为它很容易导致混乱和过于复杂的设计。不要做建筑宇航员


9

根据您的问题,我给您的印象是您对模型的角色有些模糊。该模型固定在与应用程序关联的数据上;如果应用程序具有数据库,则模型的工作就是与之交谈。它还将处理与该数据关联的任何简单逻辑;如果您有一条规则说在所有情况下TABLE.foo ==“万岁!” 和TABLE.bar ==“ Huzzah!” 然后设置TABLE.field =“ W00t!”,那么您希望模型来处理它。

控制器应该负责处理应用程序的大部分行为。因此,回答您的问题:

是否仅通过对Controller中的方法的调用将公共void actionPerformed(ActionEvent e)放入视图中?

我会说不。我说那应该存在于Controller中;View应该简单地将来自用户界面的数据输入到Controller中,然后让Controller决定应作为响应调用哪些方法。

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

您的大部分验证工作确实应该由Controller完成;它应该回答数据是否有效的问题,如果无效,则将适当的错误消息提供给视图。实际上,您可以将一些简单的健全性检查合并到“视图”层中,以改善用户体验。(我主要考虑的是网络环境,您可能希望在用户点击“ Submit”时弹出一条错误消息,而不是等待整个提交->处理->加载页面周期告诉他们搞砸了。)请小心;您不想重复不必要的工作,并且在很多环境中(同样,我在考虑网络),您通常不得不将来自用户界面的任何数据都视为一堆污秽的污秽。说谎直到你

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

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

根据我的经验,模型与视图之间的直接通信应该非常非常有限,并且视图不应直接更改任何模型数据;那应该是控制器的工作。

如果验证是在View中完成的,我该在Controller中输入什么?

往上看; 真正的验证应该在控制器中。希望您对目前应在控制器中添加的内容有所了解。:-)

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


7

控制器实际上是视图的一部分。它的工作是弄清楚需要哪些服务来满足请求,将View中的值编组为服务接口所需的对象,确定下一个View,并将响应编组为下一个View可以使用的形式。它还处理所有引发的异常,并将其呈现给用户可以理解的视图。

服务层是知道用例,工作单元和模型对象的事物。对于每种视图类型,该控制器将有所不同-您将没有用于台式机,基于浏览器,Flex或移动UI的相同控制器。所以我说它确实是用户界面的一部分。

面向服务:这就是工作要做的地方。


3

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

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

我建议你把:

public void actionPerformed(ActionEvent e)

在控制器中。然后,您视图中的动作侦听器应委派给控制器。

至于验证部分,您可以将其放在视图或控制器中,我个人认为它属于控制器。

我绝对建议您看一下Passive View and Supervising Presenter(本质上就是Model View Presenter分成的内容-至少是由Fowler进行的)。看到:

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

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


3

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

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


1

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


1

实质上,您对控制器中的内容是正确的。这是模型与视图交互的唯一方法。可以将执行的动作放置在View中,但是可以将实际功能放置在另一个充当Controller的类中。如果要执行此操作,建议您查看“命令”模式,这是一种抽象具有相同接收方的所有命令的方法。对不起,题外话。

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

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


0

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


0

因此,我们这样做了,主要使用Controller来处理和响应用户驱动的输入/动作(对于_Logic,除了视图,数据和明显的_Model以外的其他所有东西):

(1)(响应,响应-Web应用“响应”用户的行为)Blog_Controller

->主要()

-> handleSubmit_AddNewCustomer()

-> verifyUser_HasProperAuth()

(2)(“业务”逻辑,Web应用程序如何思考?)Blog_Logic

-> sanityCheck_AddNewCustomer()

-> handleUsernameChange()

-> sendEmail_NotifyRequestedUpdate()

(3)(视图,门户网站,Web应用程序的“显示方式”)Blog_View

-> genWelcome()

-> genForm_AddNewBlogEntry()

-> genPage_DataEntryForm()

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

(5)(基本数据层,读取/写入数据库)Blog_Model

-> saveDataToMemcache()

-> saveDataToMongo()

-> saveDataToSql()

->加载数据()

有时,我们对于在C或L中放置方法的位置有些困惑。但是Model坚如磐石,水晶般清晰,而且由于所有内存中数据都位于_Meta中,因此也很容易理解。顺便说一句,我们最大的飞跃是采用_Meta用法,因为它清除了各种_C,_L和_Model对象中的所有杂项,使所有这些在心理上都易于管理,而且一口气就给了我们什么称为“依赖注入”,或者一种与所有数据一起传递整个环境的方法(其好处是可以轻松创建“测试”环境)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.