【问题标题】:MVC pattern. The relationship between Model, View and ControllerMVC 模式。 Model、View、Controller的关系
【发布时间】:2015-05-14 08:14:14
【问题描述】:

Model、View 和 Controller 之间的关系让我很困惑。

本主题显示了从视图到控制器的箭头、从控制器到模型的箭头以及从模型到视图的箭头: http://www.codeproject.com/Tips/31292/MVC-v-s-MVP-How-Common-and-How-Different

但是,这个主题在模型和视图之间显示了一个双箭头; View 和 Controller 之间的双箭头;以及从控制器到模型的箭头: http://www.codeproject.com/Articles/288928/Differences-between-MVC-and-MVP-for-Beginners

最后,本主题展示了从 View 到 Model 的箭头、从 Controller 到 Model 的箭头以及从 Controller 到 View 的箭头: http://www.w3schools.com/aspnet/mvc_intro.asp

我有一些问题:

  1. 哪些关系是正确的?
  2. 业务逻辑应该在控制器还是模型中处理?我在某处读到不应将业务逻辑放在 Controller (ASP.Net MVC) 中
  3. 如果控制器将对象传递给视图,该对象是否属于模型?
  4. 视图如何直接从模型中检索数据?它是直接引用模型还是与来自控制器的模型交互?

【问题讨论】:

    标签: model-view-controller mvp


    【解决方案1】:

    我发现您链接到的所有图像都令人困惑。这张图片 (taken from Wikipedia) 解释得最好。

    工作原理

    MVC 考虑三个角色。 model 是一个对象,表示有关域的一些信息。这是一个非视觉 包含除用于 用户界面。

    视图表示模型在 UI 中的显示。因此,如果 我们的模型是一个客户对象我们的视图可能是一个充满 UI 的框架 使用模型中的信息呈现的小部件或 HTML 页面。这 视图只是信息的显示;的任何更改 信息由 MVC 三位一体的第三个成员处理: 控制器。 控制器 接受用户输入,操作模型,并使视图适当地更新。这样 UI 是 视图和控制器的组合。

    -- 引自 Martin Fowler 的 Patterns of Enterprise Application Architecture

    您的问题

    1. MVC 是关于分离关注点,而不是关于关系。
    2. 业务逻辑应该在模型中。控制器仅用于与用户交互。
    3. 是(很可能)
    4. 通常,视图会从模型中获取必要的信息。使用被动视图时,对象(来自模型)从控制器传递。重要的是视图只从模型中读取,从不写入/更新它。

      视图观察并响应模型的变化。该模型是Domain Model,而不是单个记录集或实体。

    勘误表

    目前 MVC 的普遍使用方式与 Martin Fowler 创造的原始 MVC 模式不同。他在 Smalltalk 中建立了这种模式。

    MVC 的核心,对后来的框架影响最大的想法就是我所说的Separated Presentation

    MVC 的另一部分是模型、视图和控制器如何交互。

    在这种情况下,所有视图和控制器都观察模型。当模型发生变化时,视图会做出反应。

    这与 Ruby on Rails 流行的 MVC 非常不同,后者由控制器负责准备和加载视图。

    class ArticlesController < ApplicationController
      def create
        @article = Article.new(article_params)
    
        if @article.save
          redirect_to @article
        else
          render 'new'
        end
      end
    

    Martin Fowler 将 MVC 浓缩于此

    • 在表示(视图和控制器)和域(模型)之间建立强有力的分离 - 分离的表示。
    • 将 GUI 小部件分为控制器(用于对用户刺激做出反应)和视图(用于显示模型的状态)。控制器和视图 应该(大部分)不直接沟通,而是通过模型进行沟通。
    • 让视图(和控制器)观察模型以允许多个小部件更新而无需直接通信 - 观察者 同步。

    -- 引自 Martin Fowler 的 GUI Architectures

    【讨论】:

    • 关于您的第 4 点:实际上,通常(正如您的图像和报价所示)控制器不会将对象传递给视图,但视图会从模型中获取必要的信息。这可能是由控制器的操作引起的,或者视图直接侦听模型的更改。只有在“被动视图”版本中,控制器才会将数据传递给视图。
    • @jhyot 谢谢,已修改
    • 阅读 Martin Fowlers 最近关于 MVC 的文章。问题是 Wikipedia 图片是错误的。在经典的 Smalltalk MVC 中,视图是模型的观察者。文章martinfowler.com/eaaDev/uiArchs.html
    • 请问您为什么要划掉第4题的答案?
    • 虽然 MVC 通常是这样实现的,但这并不是 Fowler 最初描述的方式。
    【解决方案2】:

    维基百科不是技术手册

    维基百科的 MVC 图像存在很多混淆。问题是在 UML 类图中A -&gt; B 表示 A 处于活动状态并调用 B(因此 A 依赖于 B)。但是维基百科社区不是纯粹的技术,并且绘制具有其他令人困惑的箭头含义的图像。有一个完整的箭头圈看起来不错且合理,不是吗?

    不。 特别是Model --updates--&gt; View 关系是可憎的。从技术上讲,它是View --reads--&gt; Model。如果模型处于活动状态,则意味着数据对象和操作依赖于系统的其他部分,因此无法重用。

    另一个废话是User --uses--&gt; Controller。控制器是不可见的,用户只使用视图,其他部分对他们来说是黑盒。 Controller基本上是事件实现的系统。事件的来源可以是用户的输入或模型的数据变化,但它们使用接口,是控制器实现它们。 (它被称为控制反转,因此有些人会混淆地绘制相反方向的箭头。)这些动作命令模型和视图进行更新,因此箭头指向控制器。没有任何东西可以控制控制器。这就是它被称为控制器的原因:所有控制都聚合到它里面。 Active View 是一个例外:例如,如果它需要填充它的选择框,它可以在没有控制器的支持下读取模型。但有时 View 对 Model 接口的依赖是不可取的,所以并不是所有的 View 都被设计为活动的。

    所以正确的图片是

          --- Controller ----
          |                 |     !!! arrows mean dependency, not data flow !!!
          V                 V
        View ------------> Model
             (if active)
    

    事件调度器

    这是维基百科造成混乱的根源:MVC 架构不能独立运行,它需要处理事件并调用控制器的 Event Dispatcher

    • 在 Web 应用程序中,它是 HTTP 服务器
    • 在托管应用程序中,它是由 IDE 自动生成的代码(通常对编码器隐藏)
    • 在本机应用程序中,它位于主循环中
    • 在较低级别的应用程序中,由系统环境提供
    User <--> View --> Event Dispatcher
                ∧            |
                |            |          !!! arrows mean data flow, not dependency !!!
             Controller <-----
    

    现在我们有了交互周期。注意箭头的含义:从依赖关系来看,Event Dispatcher当然是独立于Controller的,Controller需要一些Event Dispatcher。

    要实现 MVC,我们需要了解下面在活动模型场景中描述的依赖注入技术。

    活动模型场景

    有时模型也可能是事件的来源,即如果某些帐户信用下降到某个水平以下,它可能会向视图发出警告以显示警告。但是Model应该独立于系统的其他部分,所以它不能调用View。观察者设计模式是实现它的方式,见简化示例:

    型号

    Model 使用接口让 View 钩住其“账户太低”或“账户太高”的事件

    interface AccountObserver {
        // in dummy examples, these methods are often vaguely named update()
        public void accountLow(int value);
        public void accountHigh(int value);
    }
    
    class Model {
        // protected, not private, to make the Model extensible
        protected int account;
        protected AccountObserver observer;
    
        // more observers should be allowed, we should have array of observers
        // and name the method "register..." instead of "set..."
        public void setAccountObserver(AccountObserver o) {
            observer = o;
        }
    
        public void updateAccount(int change) {
            account+= change;
            // calculate values ...
            if(account<minValue) observer.accountLow(account);
            if(account>maxValue) observer.accountHigh(account);
        }
        ...
    }
    

    查看

    有些人会建议聚合 Observer 而不是实现它。继承更简单,如果模型定义所有观察者的方法具有唯一名称,我们就可以继承。

    class View : AccountObserver {
        public void accountLow(int value) {
            warning("Account too low! It has only "+value+" credits!");
        }
        public void accountHigh(int value) {
            warning("Account too high! It has above "+value+" credits!");
        }
        ...
    }
    

    控制器

    在架构的控制器部分,我们将用户界面(视图)和其他事件源与模型(可能包含多个数据源)放在一起。在我们最简单的情况下:

    class Controller {
        protected Model model;
        protected View view;
    
        public Controller(Model model, View view) { // Constructor
            this.model = model; this.view = view;
            model.setAccountObserver(view);
        }
    
        // called by Event Dispatcher
        void onUpdateAccount(int requestedValue) {
            if(requestedValue<0) ... // the business logic can be here or in the Model
            model.updateAccount(requestedValue); // this updates the View
        }
    }
    

    注意model.setAccountObserver(view) - 模型和视图对象(作为控制器的属性)是耦合的​​,但模型和视图是独立的。这种依赖注入模式是理解模型 - 视图关系的关键。

    现在回答您的问题

    1. 哪些关系是正确的?全部和没有。 全部,因为差异来自箭头的不同含义。 ,因为没有明确描述箭头的含义(或者像维基百科的图像一样错误,请参阅 Olexander Papchenko 的评论)。
    2. 业务逻辑应该在 Controller 或 Model 中处理? 两者都有。纯数据操作肯定是属于Model的,但是Model不能决定一切,即用户应该在什么时候、如何登录。这个属于Controller,如下代码所示。
    3. 如果Controller传递一个对象给View,这个对象是属于Model的吗?是的,看下面的代码。
    4. 视图如何直接从模型中检索数据?它是直接引用模型还是与来自控制器的模型交互?我相信这两种情况都是可能的:如果视图需要显示一些静态数据,如国家列表,如果它有实例到模型(可能通过一些接口)并直接调用它的 getter 方法(如果视图需要更改数据,它会创建一个事件并让控制器处理它)。这是上图中的箭头View --&gt; Model。如果数据是动态的,比如getContacts(int userId),则需要Controller来验证请求:
    class Controller {
        protected Model model;
        protected View view;
        protected User user;
    
        public Controller(Model model, View view) { // Constructor
            this.model = model; this.view = view;
            model.setAccountObserver(view);
            initBusinessLogic();
        }
    
        protected function initBusinessLogic() {
            user = view.loginModalDialog(); // active View (needs to get userId from Model)
            // passive View alternative
            // [login, password] = view.loginModalDialog();
            // user = model.authenticateUser(login, password);
    
            // Controller pass object from the Model to the View
            if(user.isLoggedIn()) view.setContactList(model.getContacts(user.id));
            // if(user.isLoggedIn()) view.setContactList(userId); // less universal
            // view.doYourStuff(userId); // wrong, View should not have business logic
        }
    }
    

    注意 模型和视图通常实现为具有不同职责的多个类(用于对话、主页等的视图,用于用户操作、命令等的模型)。每个应用程序只有一个控制器;如果我们对每个 View 都有专门的控制器,则称为 Presenter,架构是 MVP。

    【讨论】:

    • “视图也可以注册它的模型(在控制器之外)--->并直接调用模型的方法
    • @BoteaFlorin 你是对的,非常好的观点。在代码分离良好的情况下,视图可能只调用 getter 来设置其控件(如用数据填充选择框),其他任何事情都不是它关心的问题。如果 View 想要调用 Model 的一些设置器(例如单击提交按钮),它只会创建一个事件,而 Controller 决定要做什么(即 Controller 检查事件在当前状态下是否有效)。在 Web 应用程序中,控制器处理所有 HTTP 请求,视图创建它们(请求也可以来自其他端点)并且 HTTP 服务器充当事件调度器。
    • @BoteaFlorin 事件调度器不是控制器 BTW 的一部分。如果您的应用程序不是由 HTTP 服务器管理的,则事件调度程序通常位于主循环中。如果您在 MSVC 等 IDE 中使用可视化编程,调度程序和主循环是自动生成的,即通过调用 Application.Start(new Form1()) 之类的东西来调用 Form1 实例中的事件处理程序。
    猜你喜欢
    • 2012-02-10
    • 2012-11-01
    • 2011-01-06
    • 1970-01-01
    • 2014-01-10
    • 1970-01-01
    • 2011-05-31
    • 1970-01-01
    • 2011-12-04
    相关资源
    最近更新 更多