MVC系统是如何工作的?

21

我正在尝试学习MVC模式,但是每个地方都说不一样的话。所以现在我不知道什么才是真正的MVC。

因此,我猜想它应该是最纯粹的MVC:

  • Model只是数据并通知数据更改。
  • ViewModel读取消息以更新视图。
  • ControllerView读取用户输入并相应地更改Model

实现

  • Model不认识任何人。
  • View认识Model
  • Controller认识ViewModel

伪代码:

/* Model */
class Color{ 
  color = blue;
  setColor(color);
  notifyUpdate();
}
/* View */
class ColorPicker(model){
  model.register(update);
  update(){
    this.colorToExhibit = model.color;
  }
}
/* Controller */
class Colorize(view, model){
  view.register(update);
  update(color){
    model.setColor(color);
  }
}

一些问题:

  1. 那是正确的吗?
  2. 我不明白为什么View不能直接改变Model,而要通过Controller。
  3. 假设我有动画需要在操作后执行。谁必须处理这个动画:Model、View还是Controller?此外:动画逻辑是Model、View还是Controller的一部分?更多:假设一个扑克游戏。用户选择一个动作(比如,“加注”)之后,系统必须播放一个动画(比如,筹码从玩家位置到桌子上)。如何将这个带有动画的扑克示例视为MVC?你能解释一下并给出一个伪代码吗?

谢谢。


1
http://st-www.cs.illinois.edu/users/smarch/st-docs/mvc.html - Esailija
我怀疑你发现不同的地方“说了不同的话”关于MVC的原因是实现上存在差异。例如,David的出色回答听起来特定于iPhone开发-视图“绑定”到模型的概念不是我从PHP Web MVC系统(如Symfony)中认识的描述性术语。同样,在理论上的MVC系统中,模型不必引发事件,尽管在Objective C中会这样做。我认为你最好的选择是选择一个MVC系统并试用它。你是想做移动/应用程序开发还是Web开发?使用什么编程语言? - halfer
@halfer:实际上,我从未进行过iPhone开发。我的主要经验是Web开发,我试图努力将描述与ASP.NET MVC实现分离开来。我想这种努力取得了一定的成功 :) 通过“绑定”,我并不是指任何语言/框架特定的东西,而只是拥有一个期望在特定位置接收模型数据的骨架视图。 "绑定"的行为就是填充这些位置。不同的框架有不同的工具来完成这个任务,或者可以手动完成。 - David
@David,谢谢。确实,控制器向视图提供数据-在我看来,这不仅可以是模型数据,还可以是计算、随机、文字、缓存数据等。我想对Fabricio说的主要观点是,在控制器逻辑中使用的并非所有变量都可在视图中使用-通常程序员会指定哪些内容可用,因此两者之间的接口清晰明确。根据我的(PHP)经验,视图无法独立查看模型,除非控制器提供列值、行类或结果集合等。 - halfer
@halfer:所有的观点都很好。在这方面,我通常更喜欢在“模型”和“视图模型”之间有一个区别。模型是业务领域对象。在许多情况下,它与UI行为一对一,但并不总是如此。即使是这样,它仍然是一个不同的关注点。视图模型将包含数据的任何视图特定转换以及要执行的任何视图特定函数。在这方面,我喜欢FubuMVC的方法,“视图精确地有一个视图模型,视图模型返回精确地一个”。拥有一个轻量级的UI绑定视图模型对于这些目的非常有用。 - David
奇怪...此时此刻,有三个答案都是由一个名叫David的人回答的... - D.Shawley
3个回答

27
  • 模型只是数据并通知数据更改。
  • 视图读取模型的消息以更新视图。
  • 控制器从视图中读取用户输入并相应地更改模型。

模型不仅仅是数据。模型还包含业务逻辑。它包含系统所有智能(或至少是幕后智能的抽象,例如数据库调用或其他服务调用)。考虑这句话:“让你的模型变重,而你的控制器则变轻。”

  • 模型谁也不认识。
  • 视图认识模型。
  • 控制器认识视图和模型。

模型没有关联任何人,这是正确的。模型应该在应用程序之间具有可移植性,不能以任何方式依赖于UI方面。(在这种情况下,视图和控制器是UI问题)

视图认识模型,这也是正确的。视图基本上与模型“绑定”。它呈现所有UI元素,并根据需要将模型数据放置在UI元素中。

控制器“有点”认识视图。它知道应该向哪个视图控制,但它不知道任何关于该视图的信息。也不知道以前哪个视图来自哪里。控制器会响应事件。从UI中传入事件,携带某种状态信息(可能是ViewModel),通过模型(业务逻辑发生的地方)指导逻辑控制,并响应模型(或ViewModel,如果特定视图的数据形状与模型不同)和视图。

我不明白为什么视图不能直接通过控制器更改模型。

在用户交互的上下文中,视图可以操作模型,但不应期望这些更改以任何方式持久化。视图应该被认为是“客户端”,并且不知道任何“服务器端”的内容。(即使你谈论的是原生应用程序而不是Web应用程序)。持久化任何更改都被认为是UI“操作”或“事件”,需要由控制器执行。

假设我有一些动画需要在操作之后执行。谁必须处理此动画:模型、视图还是控制器?此外,动画逻辑属于模型、视图还是控制器的一部分?

动画听起来完全是基于UI的操作,应该位于视图中。除非有更多的事情发生,而不仅仅是UI动画。例如,如果我有一个Web应用程序,在页面加载时,我想淡入一些数据(动画)...那完全在视图中。数据会像任何其他数据一样传递到视图中,动画完全在UI(视图)中进行。从模型或控制器的角度来看,它不做任何事情。

假设有一个扑克游戏。用户选择一个动作(比如‘加注’)后,系统必须播放一个动画(比如筹码从玩家位置到桌子上)。我该如何将这个带有动画的扑克示例视为MVC?能否解释并给出相关的伪代码?

动作(“加注”)是控

View Raise(GameState state)
{
    // Interact with the Models to update the known state of the game.
    // The Models would perform the actual Poker game logic.
    // Respond with a View bound to updated Models.
}

一旦控制器通过新视图响应UI,该视图将包含任何要显示给用户的动画。(毕竟,除非操作成功,否则不希望执行动画,对吧?当控制器通过指示成功操作的新视图响应UI时,动画将播放。它可能会改为通过指示错误的视图响应UI,在这种情况下,该视图将显示其他内容。)


我对控制器“有点”了解视图感到困惑。你所说的“它应该直接控制哪个视图”是什么意思?你能详细说明一下吗? - B T
@BT:控制器有点像协调者/调度员。它接收请求或某种输入,与模型交互以根据该输入更改系统状态,并将输出引导到视图。因此,它“有点”知道视图,因为它需要知道哪个视图作为输出进行引导。相反,作为输出呈现的视图不知道刚刚呈现它的控制器的任何信息。控制器到视图的流程是单向的。 - David
好的,当你谈论一个视图时,你是在谈论一个整个的屏幕/页面,它可以显示或隐藏,对吗?这个概念能够推广到复合视图吗? - B T
@BT:最终视图是呈现给用户的UI展示。无论它由多个组件组成,是否显示/隐藏组件等都取决于您如何构建视图。不同的技术/框架等会以不同的方式处理这些概念。但最终结果是为用户提供某种交互界面。从概念上讲,这就是视图,物理上该视图可能由许多不同的元素组成,其中任何一个或多个在其各自的技术中都可以称为“视图”。 - David

18

我采用简单的银行类比。

  • 出纳员是视图(Views)。
  • 传送员是控制器(Controllers)。
  • 银行家是模型(Models)。

银行家是聪明的人,他们了解所有业务逻辑并执行所有复杂计算。

传送员用于将资金(数据)从银行家运输到出纳员处。

出纳员向客户呈现资金。

一个简单的表述:

模型(Model)

public class BankAccount
{
     public int ID;
     public int Balance;

     public BankAccount(int id)
     {
         ID = id;
         Balance = DetermineAmount();
     }

     public int DetermineAmount()
     {
         // Gather transaction info, debits, credits and return a
         // sum of the amount left in the account depending on the
         // id provided.
     }
}

控制器

    public class BankAccountController
    {

         public ViewResult Index(int id)
         {
             BankAccount account = new BankAccount(id);
             return View(account);
         }

    }
视图
<ul id="account-info">
   <li>Account ID: `@Model.ID`</li>    
   <li>Balance: `@Model.Balance`</li>
</ul>

对我来说,这个非常简单的例子完美地补充了被接受的答案。 - Dart Feld

9

如果你对历史上的MVC感兴趣,那么可以从Trygve Reenskaug开始。他在1970年代末创造(观察?归类?)了它。首先,阅读1979年的"Models-Views-Controllers"。它定义了术语。请注意它的标题 - 所有三个角色都是复数形式。这是大多数人似乎会犯错的第一件事。

我发现对于MVC最好的描述实际上在2004年的一份名为"Inside Smalltalk MVC"的演示文稿中。我猜测描述Smalltalk 80最终版本MVC的规范论文是Krasner和Pope的"A Cookbook for Using the Model-View-Controller User Interface Paradigm in the Smalltalk-80"和Steve Burbeck的"Applications Programming in Smalltalk-80: How to use Model-View-Controller (MVC)"。这两篇论文都值得一读。
如果你有些时间可以消磨,并且不介意听Robert Martin讲话,他在Ruby Midwest 2011做了一个很好的MVC主题演讲。虽然演讲时长有一小时左右,但相当有趣和启发性。我倾向于认同他的观点:大多数实现MVC的方式都是错误的。我花了一些时间寻找相关的图表,最终找到了一张我喜欢的来自Pope和Krasner的MVC图表链接。

MVC
(来源: as3dp.com)

从我的角度看,以下是关键点:

  • 模型实例负责通知感兴趣的对象进行更改。请注意,这些可以是任何对象实例。图表显示了视图和控制器都在此处接收更新。
  • 视图负责查询当前状态并显示结果。它们通常还执行过滤或数据转换。
  • 控制器负责接受用户输入并向视图转发视图消息
    • 视图消息是MVC中的一个常见主题。重要的是,它们独立于UI世界 - 这不是鼠标点击等内容,而是视图特定的事件语言。这将我们带到下一个点。
  • 视图不以任何方式依赖于控制器。控制器负责安排和创建视图,并为其余世界和视图之间提供接口。
  • 在完美的世界中,视图负责使模型表示可见。当MVC应用于桌面应用程序时,就是这样工作的。
现实情况是,MVC已经被扭曲和重写,以适应Web世界。它不再是真正的MVC,或者可能是MVC被重新定义了。这就是为什么你会看到那么多不同的MVC观点和表现形式。如果你想编写桌面风格的应用程序,那么可以看看Krasner和Pope的东西。如果你想了解MVC如何应用于Web,那么我建议大家看看Uncle Bob的主题演讲,他提出了更适合Web应用程序的另一种架构——所谓的“Interactor、Entity、Boundary Architecture”,还有与他关于“建筑的失落岁月”的演讲相关的内容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接