了解 MVC 模式

Posted

技术标签:

【中文标题】了解 MVC 模式【英文标题】:Understanding the MVC Pattern 【发布时间】:2011-03-19 14:10:01 【问题描述】:

我在理解 MVC 模式时遇到了一些麻烦。我确实知道我们正在尝试将 GUI 与业务逻辑分离,尽管我在理解如何方面遇到了问题。

据我了解,View 是用户看到的。所以它通常是窗口/表单。 Controller 介于 ViewModel 之间。控制器将使数据双向“流动”。它还会在需要时保持状态(如果我有一个包含 5 个步骤的向导,则 Controller 有责任确保它们以正确的顺序制作,等等)。 Model, 是我的应用程序逻辑的核心所在。

这个观点正确吗?

为了尝试把它变成更有意义的东西,我将尝试用 WinForms 勾勒出一个简单的例子(请不要使用 ASP.NET 或 WPF!-对于 java 人群,据我了解,Swing 有效以类似于 WinForms 的方式!),看看我是否做对了,我会提出我在做这件事时经常遇到的问题。


假设我有一个只包含一个类的模型(只是为了使它更容易。我知道这会使示例看起来很愚蠢,但这样更容易):

class MyNumbers 
    private IList<int> listOfNumbers = new List<int>  1, 3, 5, 7, 9 ;

    public IList<int> GetNumbers() 
        return new ReadOnlyCollection<int>(listOfNumbers);
    

现在是时候让我的Controller

class Controller

    private MyNumbers myNumbers = new MyNumbers();

    public IList<int> GetNumbers() 
        return myNumbers.GetNumbers();
    

View 应该只有一个 ListBox,其中包含在 MyNumbers 中检索到的所有数字作为项目。

现在,第一个问题出现了:

Controller 是否应该负责创建MyNumbers?在这个简单的情况下,我认为它可以接受(因为MyNumbers 无论如何都会做同样的事情,并且没有关联状态)。但是让我们假设我想为所有不同的控制器使用我的应用程序具有相同的MyNumbers 实例。我必须将我想使用的MyNumbers 实例传递给Controller(以及所有其他需要它的人)。谁来为此负责?在这个 WinForms 示例中,是 View 吗?或者那会是创建View 的类吗?

转个问题:这 3 个部分的实例化顺序是什么? MVC 的“所有者”调用来创建它的代码是什么? Controller 是否应该同时创建 ViewModelView 是否应该实例化 ControllerController Model

第二个问题:

假设我只希望我的应用程序具有Use Case 这个Controller 描绘的main 方法应该是什么样子?

第三:

为什么在下面的 MVC 图中,View 有一个指向Model 的箭头? Controller 不应该始终是ViewModel 之间的桥梁吗?


我还有一两个问题,但在我理解了第一个细节之后,问这些问题可能会更有意义。或者也许在我理解了第一个问题之后,所有其他问题都崩溃了。

谢谢!

【问题讨论】:

好问题 - 我仍在尝试用“正确的方式”来做 MVC 这个问题“主观性和争论性”如何? :boggle: 这是一个基于引用图的示例:***.com/questions/3072979 【参考方案1】:

“据我了解,视图是用户看到的。所以它通常是窗口/窗体。控制器位于视图和模型之间。控制器将双向“处理”数据。它将还可以在需要时保持状态(如果我有一个包含 5 个步骤的向导,那么控制器有责任确保它们以正确的顺序制作等)。模型是我的应用程序逻辑的核心所在。”

这几乎是正确的。控制器不保留数据。它调用一个持久化数据的服务。原因是,持久化数据绝不仅仅是一个保存的调用。您可能希望对数据进行验证检查,以确保其符合您的业务需求。您可能需要进行一些身份验证以确保用户可以保存数据。如果你在服务中这样做,那么你就有了一个很好的功能包,你可以一遍又一遍地使用,比如一个 webapp 和一个 web 服务。如果您在控制器中执行此操作,例如对于 Web 应用程序,那么当您编写 Web 服务时,您将不得不重构和/或复制代码。

针对您的评论“我不确定我是否完全理解您的观点。控制器是否检查 UI 输入,或者是模型检查?”

您的控制器应该只控制执行哪些业务功能路径。那就是它。控制器应该是代码中最容易编写的部分。您可以在 gui 上进行一些验证(即查看,例如确保电子邮件地址格式正确,文本输入不超过最大值),但业务层也应该验证输入 - 因为我之前提到的原因,当你开始建立更多的端点,你不必重构。

【讨论】:

我在说“句柄”时使用了不正确的术语。我的意思不是“工作”它,而是让它流动,从 View 到 Controller,再从 Controller 到 View。 我不确定我是否完全理解您的观点。 Controller 会检查 UI 输入,还是 Model 会检查? 当然。我想你明白了。我的 cmets 更多的是关于如何使用 MVC 和一些常见的设计选择,允许您编写可重用的组件。如果您的模型正在处理视图问题(例如输出 html),或者您的控制器正在处理业务逻辑(例如决定如何格式化要保存的值)等,您知道您做错了......【参考方案2】:

回答第三个问题

当模型发生变化时,它会通知视图,然后视图使用它的 getter 从模型中获取数据。

【讨论】:

这是一个问题还是一个陈述? 但是我们不是在视图和模型之间添加了不必要的耦合吗?我们不应该只使用控制器作为一种代理吗?谁有责任将 IList 数据转换为 ListBox 中的项目?视图还是控制器?控制器应该向视图显示 IList 还是应该例如引用 ListBox 并将内容添加到 ListBox 本身?谢谢 让我们看一个例子:你有重要的数据,你想把它们展示给你的用户。所以你有一个模型,可能是一个列表。视图从模型中获取列表,视图知道如何显示数据。视图可以在表格、图表等中显示它... 当您允许您的用户修改模型(添加/删除项目)时,您就有了一个按钮或其他东西,并且您调用了控制器的方法之一,控制器将修改模型。模型发生变化,通知View,View获取数据。【参考方案3】:

掌握 MVC 的最简单方法是在执行它的框架中使用它,也就是说..

模型与数据源(数据库或其他)交互,并让您可以访问您的数据。 View 与外部世界交互,它从某个地方接收输入并将数据传递给 Controller,它还侦听 Controller 以确保其显示正确的数据。 控制器是所有魔法发生的地方; Controller 操作数据、推送事件并处理双向(到/从 View 和从/从 Model)的变化。

这张图很有帮助(它比***的更有意义):

Source,还有一篇关于 MVC 的精彩文章!

【讨论】:

很好的答案 (+1)。你能提供图表的来源吗? @incrediman:来自图片的 URL:java.sun.com/developer/technicalArticles/javase/mvc 实际上,我发现那篇文章的第一张图片更清楚,因为它也解释了每个人的职责。 @BalusC:两张图不同。第一个可能有更多信息,但讨论了一种较旧类型的 MVC 实现,所以我发现第二个更有帮助。 控制器将引用 ListBox 并在事情发生变化时更新模型。 (视图从不存储任何东西,它只发送消息) 此 Oracle 链接无效。有人能传到这里吗。谢谢。【参考方案4】:

这是来自 Java,但希望它会有所帮助。

对于主要的:

public static void main(String[] args) 

       MyNumbers myNums = new MyNumbers();  // Create your Model
       // Create controller, send in Model reference.      
       Controller controller = new Controller(myNums); 

您的控制器需要对您的模型的引用。在这种情况下,控制器实际上创建了所有 Swing 组件。对于 C#,您可能希望在此处保留表单初始化,但 View/Form 需要对模型(myNums)和控制器(控制器)的引用。希望一些 C# 人可以在这方面提供帮助。视图还需要注册为模型的观察者(参见观察者模式)。

这是我的构造函数(根据您的情况进行了调整):

public NumberView(Controller controller, MyNumbers myNums)

      this.controller = controller; // You'll need a local variable for this
      this.myNums = myNums; //You'll need a local variable for this
      myNums.registerObserver(this); // This is where it registers itself

View 将工作传递给 Controller 以处理用户的操作(按钮等)。控制器决定在模型中调用/做什么。一般来说,模型然后做一些事情并改变它的状态(也许你的列表中有更多的数字。不管它做什么)。此时,模型将让其观察者知道它已更改并自行更新。然后视图去获取新数据并自行更新。这就是模型和视图谈话的原因(你的第三个问题)。

所以模型将有:

public void notifyObservers()

    for (Observer o: observers)
    
        o.update();  // this will call the View update below since it is an Observer
    

所以在视图中,你会有这样的东西:

public void update()

    setListBox(myNums.getNumbers());  // Or whatever form update you want

希望对您有所帮助。我知道它是 Java,但这个概念仍然适用。您必须阅读一些观察者模式才能完全掌握它。祝你好运!

【讨论】:

那么,控制器是在实例化视图吗? 对不起,我过早地按了进入。希望完整的答案对您有所帮助。如果您需要进一步解释,请告诉我。 所以,首先,据我了解,您指的是“MVC 1”,如我原来的帖子图所示,而不是 indieinvader 的,对吧? 这是我认为的经典MVC,所以我猜是MVC 1。它基于您帖子中的图表。答案来自 Head First Design Patterns,它适应了我的情况,恰好与您的情况相似,模型名称根据您的情况进行了更改和轻微更改。 我实际上不使用 Java 库中的 Observable 类(因为继承问题)。我创建了自己的名为 Observable(和 Observer)的接口,这几乎是一回事。当我在互联网上发布时,这令人困惑。希望这有助于它看起来不那么“糟糕”。它确实工作得很好。【参考方案5】:

控制器是否应负责 用于创建 MyNumbers?

我会说'绝对不是。'

如果 MVC 模式旨在解耦 M、V 和 C 元素,如果 C 简单地使用 new MyNumbers() 实例化 M,这将如何工作?

在 Java 中,我们会在这里使用 Spring Framework 之类的东西。您需要一种在配置文件或其他合适的地方(不在编译代码中)表达依赖关系的方法——或者更确切地说,它是如何实现的细节。

但这个问题还有另一个因素:您可能不应该使用您打算使用的具体运行时类型来定义 myNumbers 变量(在 C 中)。使用一个接口或一个抽象类,并让它保持开放,以了解实际的运行时类型是什么。这样,将来您可以重新实现 IMyNumbers 接口以满足新出现的需求(那些您今天不知道的需求),并且您的 C 组件将继续完美地工作,没有什么比这更明智的了。

【讨论】:

你说得很好。我想远离框架,直到我明白如何自己实现它!之后,我会寻找框架。 @devoured elysium -- 你打赌!我了解您提供(并想讨论)一个非常简单的示例。但是在这样的情况下,简单的示例可能会产生误导,如果它们简单到甚至没有太多动机使用 MVC 模式的程度。在现实世界的场景中,像 MVC 这样的模式有很多理由。【参考方案6】:

为什么在下面的 MVC 图中,View 有一个指向 Model 的箭头? Controller 不应该一直是 View 和 Model 之间的桥梁吗?

它是MVC模型2。你通常可以在Java企业应用程序中看到它 其中 CONTROL 处理业务以及处理来自/到 MODEL 的数据,并选择将哪个 VIEW 呈现回客户端。渲染到客户端时,VIEW 将使用来自 MODEL 的数据:

(来源:blogjava.net)

这是一个如何从一个 JSP(VIEW) 文件中访问数据的示例,该文件是一个 bean (MODEL):

class Person String name; // MODEL
My name is $bean.name     // VIEW

【讨论】:

【参考方案7】: View 绘制模型并将其呈现给用户 控制器处理用户输入并将其转换为对模型的修改 模型保存数据和修改逻辑

没有必要将其转换为代码。反正你也不会直接说对的。

【讨论】:

【参考方案8】:

我将尝试从技术相对较少的立场来回答这个问题。我将尝试通过一个一般示例。

Controller 控制使用什么view。因此,假设您正在写入页面,controller 将引导您到 input view(例如),而如果您正在阅读同一页面,它将引导您到它的 success view(例如)。

在写入页面后,controller 会将这些参数传递给相关的model,其中与必须对它们执行的操作相关的逻辑所在的位置。如果有错误,那么controller 将引导您到error view

我的知识基于我在 Agavi 的一个月经验。希望这会有所帮助。

【讨论】:

最终用户随时查看各种视图。【参考方案9】:

查看

用户界面/负责输入输出/一些验证/需要有一种方法来通知外界 UI 级事件

只知道模型

型号

数据结构/表示呈现的数据/不应包含业务逻辑(最多可能只是一些数据/结构验证) 只知道自己(想想只有 Name 和 Age 的 Person 类)

控制器

负责业务逻辑/它创建视图并将各自的模型粘合到它们上/必须能够响应视图事件/访问应用程序的其他层(持久性/外部服务/业务层等)

什么都知道(至少是视图和模型),并且负责将所有东西粘合在一起

【讨论】:

我不明白为什么模型不应该包含任何业务逻辑。不应该是拥有所有业务逻辑的模型吗? 该模型应该传输应用程序逻辑的多个层(数据访问、业务、UI 等)。此外,可以在多个业务场景中使用相同的模型(例如,具有所有相关数据的人员)。这就是为什么将模型与不同的业务事务/上下文分开是一种很好的做法。【参考方案10】:

至于我帖子中的批评,我想我会发表一篇关于我如何倾向于在 php 中创建 MVC 模式的帖子

在 PHP 中,我将框架分为几个部分,其中一些在 MVC 中是正常的。

初选:

控制器 型号 查看

次要性 - 模型层

视图加载器 图书馆 错误层

在控制器中,我通常允许所有访问辅助层以及来自主层的视图和模型。

这是我的结构方式

|---------|       |------------|       |------------|
| Browser | ----> | Controller | ----> |   Model    |
|---------|       |------------|       |------------|
     |                  |   |                |
     |                  |   |----------------|
     |                  |
     |            |------------|
     -------------|    View    |
                  |------------|

从我的图表中,我通常绕过View &lt;-&gt; Model 连接并执行Controller &lt;-&gt; Model,然后来自Controller &lt;-&gt; View 的链接分配数据。

在我的框架内,我倾向于创建一个对象存储系统,以便我可以轻松地获取对象等等。我的对象存储的一个例子是这样的

class Registry

   static $storage = array();

   public static function get($key)
   
       return isset(self::storage[$key]) ? self::storage[$key] : null;
   

   public static function set($key,$object)
   
       self::"storage[$key] = $object;
   

大纲更高级一些,所以当我第一次初始化对象时,我将它们存储为Registry::set("View",new View());,以便始终可以访问。

所以在我的控制器中,我创建了几个魔术方法 __get() __set() 的基本控制器,这样任何扩展控制器的类我都可以轻松地返回请求,例如:

abstract class Controller

   public function __get($key)
   
       //check to make sure key is ok for item such as View,Library etc

       return Registry::get($key); //Object / Null
   

还有用户控制器

class Controller_index extends Controller

    public function index()
    
       $this->View->assign("key","value"); // Exucutes a method in the View class
    

模型也将被放入注册表,但只允许从 ModelLayer 调用

class Model_index extends ModelLayer_mysql


class Model_index extends ModelLayer_MySqli


或文件系统

class Model_file extends ModelLayer_FileSystem


这样每个类都可以特定于存储类型。

这不是传统类型的 MVC 模式,但可以称为 Adoptive MVC。

不应将其他对象(例如 View Loader)放入注册表,因为那里不是专门针对用户的兴趣,而是由其他实体(例如 View)使用

abstract class ViewLoader

   function __construct($file,$data) //send the file and data
   
       //Include the file and set the data to a local variable
   

   public function MakeUri()
   
       return Registry::get('URITools')->CreateURIByArgs(func_get_args());
   

由于模板文件包含在视图加载器中而不是视图类中,因此它将用户方法与系统方法分开,并且还允许在视图本身中使用方法以实现一般逻辑。

模板文件示例。

<html>
   <body>
      <?php $this->_include("another_tpl_file.php"); ?>
      <?php if(isset($this->session->admin)):?>

          <a href="<?php echo $this->MakeUri("user","admin","panel","id",$this->session->admin_uid) ?>"><?php echo $this->lang->admin->admin_link ?></a>

      <?php endif; ?>
   </body>
</html>

我希望我的例子能帮助你更多地理解这一点。

【讨论】:

以上是关于了解 MVC 模式的主要内容,如果未能解决你的问题,请参考以下文章

Javaweb的MVC模式和三层架构(框架了解)

深入了解php框架mvc设计模式的原理

MVC:我需要了解模型

编写PHP框架,深入了解MVC运行流程

设计模式MVC

MVC 设计模式。 View如何适应它?