维基百科不是技术手册
维基百科的 MVC 图像存在很多混淆。问题是在 UML 类图中A -> B 表示 A 处于活动状态并调用 B(因此 A 依赖于 B)。但是维基百科社区不是纯粹的技术,并且绘制具有其他令人困惑的箭头含义的图像。有一个完整的箭头圈看起来不错且合理,不是吗?
不。 特别是Model --updates--> View 关系是可憎的。从技术上讲,它是View --reads--> Model。如果模型处于活动状态,则意味着数据对象和操作依赖于系统的其他部分,因此无法重用。
另一个废话是User --uses--> 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) - 模型和视图对象(作为控制器的属性)是耦合的,但模型和视图类是独立的。这种依赖注入模式是理解模型 - 视图关系的关键。
现在回答您的问题
-
哪些关系是正确的?全部和没有。 全部,因为差异来自箭头的不同含义。 无,因为没有明确描述箭头的含义(或者像维基百科的图像一样错误,请参阅 Olexander Papchenko 的评论)。
-
业务逻辑应该在 Controller 或 Model 中处理? 两者都有。纯数据操作肯定是属于Model的,但是Model不能决定一切,即用户应该在什么时候、如何登录。这个属于Controller,如下代码所示。
-
如果Controller传递一个对象给View,这个对象是属于Model的吗?是的,看下面的代码。
-
视图如何直接从模型中检索数据?它是直接引用模型还是与来自控制器的模型交互?我相信这两种情况都是可能的:如果视图需要显示一些静态数据,如国家列表,如果它有实例到模型(可能通过一些接口)并直接调用它的 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
}
}
注意 模型和视图通常实现为具有不同职责的多个类(用于对话、主页等的视图,用于用户操作、命令等的模型)。每个应用程序只有一个控制器;如果我们对每个 View 都有专门的控制器,则称为 Presenter,架构是 MVP。