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

时间:2009-06-19 00:01:27

标签: java model-view-controller

我想我理解MVC的基本概念 - 模型包含应用程序的数据和行为,View负责将其显示给用户,Controller负责处理用户输入。我不确定的是 在控制器中的内容。

让我们说例如我有一个相当简单的应用程序(我特别想到Java,但我认为相同的原则适用于其他地方)。我将代码组织成3个名为app.modelapp.viewapp.controller的包。

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

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

那么我实际上在Controller中放了什么?我只需要调用Controller中的方法,就可以将public void actionPerformed(ActionEvent e)放在View中吗?如果是这样,是否应在控制器中进行任何验证?如果是这样,我如何将错误消息反馈回View - 如果再次通过Model,或者Controller是否应该直接将其发送回View?

如果在视图中完成验证,我应该在Controller中添加什么?

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

13 个答案:

答案 0 :(得分:478)

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

以对话形式:

查看:“嘿,控制器,用户刚刚告诉我他要删除第4项。”
控制器:“嗯,检查了他的凭据后,他被允许这样做......嘿,模型,我希望你得到第4项并做任何你要做的事情来删除它。”
模型:“第4项......得到它。它被删除了。回到你身边,控制器。”
控制器:“在这里,我将收集新的数据集。返回给您,查看。”

查看:“很酷,我现在会向用户展示新设置。”

在该部分的末尾,您有一个选项:视图可以发出单独的请求,“给我最新的数据集”,因此更纯粹,或者控制器隐式返回新的数据集“删除”操作。

答案 1 :(得分:64)

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

控制器是用户界面的输入机制,在视图中经常被纠缠在一起,特别是对于GUI。然而,视图是输出,控制器是输入。视图通常可以在没有相应控制器的情况下工作,但是如果没有视图,控制器通常就没用了。用户友好的控制器使用视图以更有意义,更直观的方式解释用户的输入。这就是它使控制器概念与视图难以分离的原因。

将密封盒中检测区域的无线电控制机器人视为模型。

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

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

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

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

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

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

  

......如果有任何验证等   在控制器中完成?如果是这样,怎么做   我将错误消息反馈给了   查看 - 应该通过   再次建模,或者应该是控制器   只是直接发送回View?

     

如果在View中完成验证,   我在控制器中放了什么?

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

对此的酸性测试是问自己一个独立的观点(即另一个房间里通过网络观察机器人位置的人)是否应该看到任何其他人的验证错误(例如那个人)在壁橱里试图告诉机器人走出场地)。通常,答案是否定的 - 验证错误阻止了状态转换。如果没有状态tranistion(机器人没有移动),没有必要告诉其他意见。壁橱里的那个人没有得到任何他试图导致非法转换的反馈(没有视图 - 糟糕的用户界面),没有其他人需要知道这一点。

如果有触摸屏的人试图将机器人送出现场,他会得到一条很好的用户友好消息,要求他不要通过将其发送到检测区域来杀死机器人,但同样,没有其他人需要知道这个

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

答案 2 :(得分:23)

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

它声明......

  

控制器 - 控制器转换   与观点的互动   模型要执行的操作。

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

还有另一件好事article at Fowler

答案 3 :(得分:16)

MVC模式只是希望您将演示文稿(= view)与业务逻辑(=模型)分开。控制器部分只会引起混淆。

答案 4 :(得分:9)

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

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

“我是否只在调用Controller中的方法时将公共void actionPerformed(ActionEvent e)放在视图中?”

我会说不。我会说应该住在控制器中; View应该简单地将来自用户界面的数据提供给Controller,并让Controller决定应该响应哪些方法。

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

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

“如果是这样,我如何将错误消息反馈回View - 如果再次通过Model,或者Controller是否应该直接将其发送回View?”

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

根据我的经验,模型和视图之间的直接通信应该非常非常有限,并且视图不应该直接改变模型的任何数据;那应该是财务总监的工作。

“如果在视图中完成验证,我应该在Controller中添加什么内容?”

见上文;真正的验证应该在Controller中。希望你现在知道应该在Controller中放置什么。 : - )

值得注意的是,它可以在边缘处变得有点模糊;与大多数像软件工程一样复杂的东西一样,判断调用也会比比皆是。只需使用您的最佳判断,尝试在此应用程序中保持一致,并尝试将您学到的课程应用于下一个项目。

答案 5 :(得分:9)

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

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

答案 6 :(得分:7)

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

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

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

答案 7 :(得分:3)

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

不幸的是,它有时最终会与视图混合在一起 - 在小应用程序中虽然这不是太糟糕。

我建议你把:

public void actionPerformed(ActionEvent e)

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

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

我肯定会建议你看看Passive View和Supervising Presenter(这实际上是模型视图演示者分成的 - 至少是Fowler)。参见:

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

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

答案 8 :(得分:3)

以下是我使用的经验法则:如果是具体用于页面上的操作的过程,则它属于控制器而不是模特。该模型应该只为数据存储提供连贯的抽象。

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

答案 9 :(得分:1)

还要注意,每个Swing小部件都可以被认为包含三个MVC组件:每个组件都有一个Model(即ButtonModel),一个View(BasicButtonUI)和一个Control(JButton本身)。

答案 10 :(得分:1)

您对控制器中的内容基本上是正确的。这是模型与View交互的唯一方式。 actionperformed可以放在View中,但实际的功能可以放在另一个充当Controller的类中。如果您要这样做,我建议您查看命令模式,这是一种抽象所有具有相同接收器的命令的方法。对不起该题外话。

无论如何,正确的MVC实现仅具有以下交互: 型号 - >视图 查看 - >调节器 控制器 - >图

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

答案 11 :(得分:0)

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

答案 12 :(得分:0)

我们这样做,使用控制器主要处理和响应用户驱动的输入/操作(和_Logic用于其他一切,除了视图,数据和明显的_Model东西):

(1)(响应,反应 - webapp“响应用户”做了什么) Blog_Controller

- > main()的

- > handleSubmit_AddNewCustomer()

- > verifyUser_HasProperAuth()

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

- > sanityCheck_AddNewCustomer()

- > handleUsernameChange()

- > sendEmail_NotifyRequestedUpdate()

(3)(观点,门户网站,webapp如何“出现”) Blog_View

- > genWelcome()

- > genForm_AddNewBlogEntry()

- > genPage_DataEntryForm()

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

(5)(基本数据层,对DB的读/写) Blog_Model

- > saveDataToMemcache()

- > saveDataToMongo()

- > saveDataToSql()

- > loadData()

有时我们会对在C或L中放置方法的位置感到有些困惑。但是模型是坚如磐石的,清晰的,并且因为所有内存中的数据都驻留在_Meta中,所以它是一个明智的选择。那里也是。顺便说一下,我们最大的飞跃就是采用_Meta使用,因为这清除了来自各种_C,_L和_Model对象的所有碎片,使得它在精神上易于管理,而且,一下子,它给了我们什么样的东西。称为“依赖注入”,或者是一种传递整个环境以及所有数据的方法(其奖励很容易创建“测试”环境)。