维基百科不是技术手册
维基百科的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)
-模型和视图
物体
(作为控制器的属性)是耦合的,但模型和视图
类
是独立的。这种依赖注入模式是理解模型-视图关系的关键。
现在回答你的问题
-
哪些关系是正确的?
全都没有。
全部的
,因为不同之处来自于箭头的不同含义。
没有一个
,因为他们的箭头的含义没有明确描述(或者像维基百科的图片一样错误,请参见Olexander Papchenko的评论)。
-
业务逻辑应该在控制器或模型中处理?
二者都纯数据操作肯定属于模型,但模型不能决定一切,即用户何时和如何登录。这属于控制器,如下面的代码所示。
-
如果控制器将对象传递给视图,该对象是否属于模型?
是的,请参见下面的代码。
-
视图如何直接从模型中检索数据?它是否直接引用模型或与来自控制器的模型交互?
我相信这两种情况都是可能的:如果视图需要显示一些静态数据,如国家列表,我认为如果它有一个模型实例(可能通过一些接口)并调用它
吸气剂,吸气剂
方法(如果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。