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

MVC pattern. The relationship between Model, View and 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

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

我有一些问题:

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

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

How It Works

MVC considers three roles. The model is an object that represents some information about the domain. It's a nonvisual object containing all the data and behavior other than used for the UI.

The view respresents the display of the model in the UI. Thus, if our model is a customer object our view might be a frame full of UI widget or an HTML page rendered with information from the model. The view is only about display of information; any changes to the information are handled by the third member of the MVC trinity: the controller. The controller takes user input, manipulates the model, and causes the view to update appropriately. In this way UI is a combination of the view and the controller.

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

您的问题

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

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

勘误表

目前 MVC 的常用方式与 Martin Fowler 创造的原始 MVC 模式不同。他在 Smalltalk.

中建立了这种模式

At the heart of MVC, and the idea that was the most influential to later frameworks, is what I call Separated Presentation.

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

In this case all the views and controllers observe the model. When the model changes, the views react.

这与 Ruby 在 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 浓缩为这样

  • Make a strong separation between presentation (view & controller) and domain (model) - Separated Presentation.
  • Divide GUI widgets into a controller (for reacting to user stimulus) and view (for displaying the state of the model). Controller and view should (mostly) not communicate directly but through the model.
  • Have views (and controllers) observe the model to allow multiple widgets to update without needed to communicate directly - Observer Synchronization.

-- 引自 GUI Architectures 作者 Martin Fowler

维基百科不是技术手册

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

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

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

所以正确的图像是

      --- 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 <-----

现在我们有了互动周期。注意箭头的意思:从依赖的角度来看,Event Dispatcher当然是独立于Controller的,Controller需要一些Event Dispatcher。

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

活动模型场景

有时模型也可能是事件的来源,即如果某些帐户信用下降到某个水平以下,它可能会向视图发出信号以显示警告。但是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!");
    }
    ...
}

控制器

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

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) - 模型和视图 对象(作为控制器的属性)是耦合的​​,但是模型和视图 classes 是独立的。这种依赖注入模式是理解模型-视图关系的关键。

现在回答你的问题

  1. 哪些关系是正确的?全部和none。 全部,因为箭头的含义不同。 None,因为他们箭头的意思没有明确描述(或者像维基百科的图片一样错误,见Olexander Papchenko的评论)。
  2. 业务逻辑应该在控制器或模型中处理?两者。纯数据操作肯定属于Model,但Model不能决定一切,即用户应该何时登录,如何登录。这属于Controller,如下代码所示。
  3. 如果Controller传递一个对象给View,这个对象属于Model吗?是的,看下面的代码。
  4. 视图如何直接从模型中检索数据?它是直接引用模型还是与来自 Controller 的模型交互? 我相信这两种情况都是可能的:如果视图需要显示一些静态数据,如国家列表,如果它有,我认为没有错模型的一个实例(可能通过一些接口)并直接调用它的 getter 方法(如果视图需要更改数据,它会创建一个事件并让控制器处理它).也就是上图中的箭头View --> 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
    }
}

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