代码之家  ›  专栏  ›  技术社区  ›  Jacob Phan

MVC模式。模型、视图和控制器之间的关系

  •  8
  • Jacob Phan  · 技术社区  · 10 年前

    模型、视图和控制器之间的关系让我感到困惑。

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

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

    最后,本主题显示了从视图到模型的箭头、从控制器到模型和从控制器到视图的箭头: http://www.w3schools.com/aspnet/mvc_intro.asp

    我有一些问题:

    1. 哪些关系是正确的?
    2. 业务逻辑应该在控制器或模型中处理?我在某个地方读到,业务逻辑不应该放在Controller(ASP.NetMVC)中
    3. 如果控制器将对象传递给视图,该对象是否属于模型?
    4. 视图如何直接从模型中检索数据?它是否直接引用模型或与来自控制器的模型交互?
    2 回复  |  直到 10 年前
        1
  •  9
  •   Arnold Daniels    6 年前

    我发现你链接到的所有图像都令人困惑。此图像( taken from Wikipedia )最好的解释。

    MVC diagram

    它的工作原理

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

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

    --引用自 Patterns of Enterprise Application Architecture 作者:Martin Fowler

    你的问题

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

      视图观察并响应模型中的更改。模型是 Domain Model 而不是单个记录集或实体。

    勘误表

    MVC在当前的普遍使用方式与Martin Fowler提出的原始MVC模式不同。他将这种模式建立在 Smalltalk公司 .

    MVC的核心,以及对后来的框架最有影响的思想,就是我所说的 Separated Presentation .

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

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

    这与MVC非常不同 Ruby on Rails ,其中控制器负责准备和加载视图。

    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小部件分成控制器(用于响应用户刺激)和视图(用于显示模型状态)。控制器和视图 应该(大部分)不直接交流,而是通过模型交流。
    • 让视图(和控制器)观察模型,以允许多个小部件更新,而无需直接通信-观察者网 同步。

    --引用自 GUI Architectures 作者:Martin Fowler

        2
  •  7
  •   Jan Turoň    3 年前

    维基百科不是技术手册

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

    尤其是 Model --updates--> View 这种关系令人厌恶。从技术上讲,它是 View --reads--> Model 。如果模型是活动的,则意味着数据对象和操作依赖于系统的其他部分,因此不能重用。

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

    所以正确的图像是

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

    事件调度器

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

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

    现在我们有了交互循环。注意箭头的含义:从依赖性角度来看,事件分派器当然独立于控制器,控制器需要一些事件分派器。

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

    活动模型场景

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

    模型

    模型使用接口让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);
        }
        ...
    }
    

    看法

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

    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. 业务逻辑应该在控制器或模型中处理? 二者都纯数据操作肯定属于模型,但模型不能决定一切,即用户何时和如何登录。这属于控制器,如下面的代码所示。
    3. 如果控制器将对象传递给视图,该对象是否属于模型? 是的,请参见下面的代码。
    4. 视图如何直接从模型中检索数据?它是否直接引用模型或与来自控制器的模型交互? 我相信这两种情况都是可能的:如果视图需要显示一些静态数据,如国家列表,我认为如果它有一个模型实例(可能通过一些接口)并调用它 吸气剂,吸气剂 方法(如果View需要更改数据,它会创建一个事件并让Controller处理)。这是箭头 View --> Model 在上图中。如果数据是动态的 getContacts(int userId) ,它需要控制器验证请求:
    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
        }
    }
    

    笔记 模型和视图通常被实现为具有独立职责的多个类(用于对话、主页等的视图,用于用户操作、订单等的模型)。每个应用程序只有一个控制器;如果我们对每个视图都有专门的控制器,那么它被称为Presenter,架构是MVP。