深刻理解MVC与MVP

http://www.cnblogs.com/seaky/archive/2011/04/06/1982533.htmlphp

在深刻分析MVC和MVP以前,咱们有必要回顾下经典的三层架构。分层是计算机学科解决许多问题的法宝。在企业应用和互联网应用中,分层架构获得了很是普遍的应用。3层架构是各类层架构的基础,3层架构简单描述以下:html

展现层:展现层有两个职责前端

1负责展现业务数据web

2提供用户输入的接口ajax

业务逻辑层:业务逻辑层的职责是接受展现层的输入,并通过业务处理逻辑,返回业务数据。spring

数据访问层:数据访问层提供系统数据的存取服务。数据库

从架构到实现是存在一些"距离"的,架构的实现是要基于应用场景,实现方案也有所不同。数组

本文考虑两种应用场景来讨论MVC和MVP:浏览器

第一是c/s架构,也就是所谓的胖客户端,像RIA属于这类。Flex,ajax等技术均可以归结到RIA,手机APP,winform链接到远程服务的应用均可以归结这一类。服务器

第二是b/s架构(某些web程序部分的利用了ajax,排除利用ajax的这部分),也就是瘦客户端,b是指浏览器,在b/s架构中http协议是客户端和服务端通讯的惟一协议,并且是经过浏览器来展现数据的。

基于层的架构的实现中的一个问题就是层与层之间如何通讯,以及如何在层间传递数据。

首先看看数据访问层和业务层之间的通讯和数据传递。

(注意,数据访问层不保存持久数据,数据访问层是访问持久数据的接口。持久数据通常位于独立数据服务器之中,好比数据库,索引数据等等。)

在物理上,数据访问层和业务逻辑层一般部署在同一台服务器上,因此它们之间的通讯属于进程内数据传递,传递的是业务对象,速度很快,可是数据访问层 访问持久数据相对很慢的。数据访问层目标就是尽量的快的存储业务对象,而业务模型的设计会影响业务对象的存储速度,因此数据访问层的设计和实现一般是在 业务模型和数据访问策略之间进行权衡。关于数据访问层设计也有几种模式,关于这方面请自行查阅相关资料。

下面咱们看看本文的重点:

业务层和视图层的通讯,咱们经常使用的是MVC模式。MVC模式是展现层模式(注:这句话是有问题的),有必要细致的分解下展现层的职责:

1 渲染界面。在b/s架构中体如今浏览器解析html而后绘制相应的图形。在c/s架构中,好比winform程序中利用控件的状态调用本地GDI接口绘制窗口,填充数据。

在b/s和c/s中,这部分工做只能在客户端完成。

2 执行展现逻辑。依据业务层返回的数据来生成渲染界面所需的数据。

举例说明下,在b/s中,假设业务层返回字符串数组,在展现层须要使用列表进行展现,那么咱们必须利用这个数组生成特定的html,这个过程就是展现逻辑。

在b/s架构中,展现逻辑位于服务端。

在c/s架构中这部分工做在客户端进行。好比在客户端利用ajax获取字符串数组后,利用js生成html。这个展现逻辑是在客户端完成的。在winform程序中利用控件的方法设置相应的值。

3 获取输入。在c/s和b/s之中都经过客户端程序来获取数据,好比浏览器提供的输入框,winform提供的文本控件。在c/s和b/s中这部分工做只能客户端完成。

4 引起事件。事件是触发某种业务的的起点,业务层老是在某个事件到来时被调用,事件老是携带某些用户输入的信息。

在b/s架构中,浏览器发送http请求表明着事件,而在服务器web服务器在监听http请求到达这个事件,分析http请求携带的信息,交给相应的程序处理。

在c/s架构中,事件更好理解。好比点击按钮是个事件,文本框的内容改变能够定义为一个事件,文本框的内容能够做为事件的信息。

5 处理事件。展现层还有个很是重要的任务就是调用业务层接口,当事件发生时总有个程序会处理,这段程序会调用真正的业务层。

在b/s架构中,举个例子,一段php脚本从请求读取参数,利用参数调用业务,而后生成html,这段程序就是事件处理的程序,它是属于展现层,而不是业务层的。为何?

在c/s架构中更加明显,像winform程序的onclick事件处理器。

在b/s架构中事件的处理是在服务端,由于http请求事件是在服务端处理,而在c/s架构中事件的处理是在客户端。

下文中分别以 职责1-5表明上面的职责,其中最重要的是2和5。

 
MVC最初是smalltalk-80中提出的请参见文章 http://st-www.cs.illinois.edu/users/smarch/st-docs/mvc.html  
Applications Programming in Smalltalk-80(TM): How to use Model-View-Controller (MVC).
这篇文章提出了定义了MVC各自表明什么:
M是指系统数据对象的管理者,包含业务逻辑。你能够把它想象成一个门面或者一组服务。它们的返回是业务数据,它们不关心这些数据如何被使用。
C是指控制器,用于管理用户的事件,包括将处理事件,以及通知视图如何改变。
V是指视图,负责控制数据的展现,以及发起事件。
注意V和C都是位于三层架构中的展现层。而M一般是属于业务逻辑层。
最初的MVC起源于GUI程序设计。
 
在GUI程序中,用户的点击行为都会使得操做系统产生事件,最开始的作法是在view层处理这个事件(职责5),获取而且执行展现逻辑(职责2)。
随着程序的庞大,事件处理逻辑和展现逻辑混合在一块儿不利于维护,因此将职责5和职责2分离到不一样的对象之中,controller是执行职责5。view执行职责2。
 
 
此时view须要和controller存在双向引用,事件发生时,view将事件处理逻辑代理到controller中,controller反过来使用Model获取数据,调用view的展现逻辑方法,刷新界面。
存在 被动的MVC和主动的MVC两种类型
被动的MVC的特色是controller负责通知视图去改变。而 在主动的MVC之中,Model会经过观察者模式间接的和视图关联,当model变化时通知view去改变
 
 
 
 
 
 
 
 
 
 
 

下面咱们看看在b/s架构的应用环境之中MVC有着怎么样的变化。

在b/s场景之下,用户在浏览器中的任何行为致使的事件最终都会转换为http请求,因此只存在单一的事件,因此第一步是咱们须要依据事件携带的信 息将这个http请求转发给相应的事件处理器。事件处理器在获取业务数据后,须要通知视图去改变,这个过程经过一个http响应来实现,而http响应体 就是视图渲染所需的信息,在利用业务数据生成http响应体的过程实际上就是执行展现逻辑的过程。因为http协议是无状态的,因此在基于http协议中 实现真正的主动MVC模式是比较困难的,通常的思路有两种:a长链接 b轮询方式

目前在b/s架构下有两种常见的实现MVC的模式,一种叫前端控制器,一种叫页面控制器

前端控制器实现之中,存在一个类负责处理http请求事件,这个类就叫前端控制器,这个类主要工做就是依据请求信息将这些请求转发给相应的控制器来 处理事件。每一个控制器处理结束以后会返回一些信息(职责5),而后前端控制器使用视图引擎来生成所需的http响应体(职责2)。前端控制器将职责2和职 责5代理给不一样的类。

如今主流MVC框架都是采用这种,好比spring  mvc,zend等等。(我的十分推崇spring MVC,建议你们都去阅读下源码)

页面控制器更加好理解,每一个页面一个控制器。全部该页面可以发生的事件都在这个控制器内响应。好比asp.net中的Page类是典型的页面控制 器。页面控制器的缺点是像一些公用的功能很难在各个控制器中复用,只能采起继承的方式,致使复杂的类体系,并且继承的方式是很难维护。

而在c/s架构之中,事件是多样的。点击按钮,勾选复选框等等都是事件,如何管理事件是在c/s实现mvc的核心。

在winform程序中,每一个事件都有一个处理器(事件委托),这个处理器一般承担了职责2和职责5。

因此对于winform来说,MVC实现的很是很差,在winform之中要实现职责2和职责5的分离必须抛弃winform的思惟本身实现,或者借助相应的框架实现。

咱们再看一下gxt(ext GWT)它提供一个MVC框架。

MVC框架中核心就是事件的分对于一个MVC框架应该处理的是应用事件而不是系统事件。系统事件是指用户操做中由操做系统产生的事件,而应用事件是每一个应用程序自定义的一些事件,好比点击登陆按钮系统事件是Click,而应用事件多是AppEvent.LonIn。

gxt的Dispatcher类就是将应用级事件分发到各个控制器之中,在程序启动时须要将控制器加入到Dispatcher之中。

控制器Controller类包含其所支持的事件的列表,并包含相应的视图。在初始化时须要将支持的事件注册到事件列表之中。Dispatcher在分发事件时会调用

controller类的handleEvent方法,在handleEvent里面依据不一样的事件来使用相应的方法来处理。controller类能够向视图发送事件。

View类包含一个Controller用于向上引起事件,view类包含其所支持的事件处理程序。

在gxt框架之中,controller类负责职责5,而view类能够用于负责职责2。很方便的作到职责的分离。

既然有了MVC,为什么又提出MVP?在我看来MVP不过是MVC加上一些约束而已,MVC之中,V能够和M有联系,可是在MVP之中,V没必要知道 M,在MVP之中强制要求职责2和职责5的分离以及M和V的分离。而在MVC中,某些状况下V还能够调用M来获取数据,但这样没能彻底分离职责2和职责 5。

当事件到来时在MVC和MVP之中,都须要将事件分发给相应的C或者P

在C和P之中都需和M交互获取数据。可是在V之中有很大的不一样,在MVC之中,V能够响应某些事件并能够调用M获取数据。

而在MVP之中,V彻底是被动的,P经过V提供的展现接口来控制V并经过接口的参数传递数据给V。由于V中须要将任何事件的处理委托给P,因此V须要暴露回调接口给P。

举一个例子,MVP中登陆窗口V的接口定义:

复制代码
publicinterface LoginView {


void setUserName(String username);

String getUserName();

void setPassword(String pwd);

String getPassword();

void showErrorMsg(String msg);

void setLoginCallback(EventHandler handler);

Widget asWidget();
}
复制代码

实现

复制代码
publicclass LoginViewImpl extends Composite implements LoginView {

privatefinal TextField<String> username =new TextField<String>();
privatefinal TextField<String> password =new TextField<String>();
privatefinal Button login =new Button();

public LoginViewImpl() {
FormPanel layout =new FormPanel();
initWidget(layout);
layout.add(username);
layout.add(password);
layout.add(login);
layout.setSize(500, 500);
password.setPassword(true);
username.setFieldLabel("username:");
password.setFieldLabel("password:");
login.setText("登陆");
}

publicvoid setUserName(String username) {
this.username.setValue(username);

}

public String getUserName() {
returnthis.username.getValue();
}

publicvoid setPassword(String pwd) {
this.password.setValue(pwd);
}

public String getPassword() {
returnthis.password.getValue();
}

publicvoid showErrorMsg(String msg) {
com.google.gwt.user.client.Window.alert(msg);
}

publicvoid setLoginCallback(final EventHandler handler) {
login.addListener(Events.Select, new Listener<ButtonEvent>() {
publicvoid handleEvent(ButtonEvent be) {
handler.handle();
}
});
}

}
复制代码

 其中EventHandler是当用户点击登陆按钮时的回调。EventHandler的实现是在LoginPresenter之中的,对于LoginView的实现只需在onclick事件中回调便可。

对应的presenter代码以下:

复制代码
publicclass LoginPresenter implements Presenter {

private LoginView view;
privatestaticfinal String LOGIN_CHECK_URL ="../check";

public LoginPresenter() {
this.view =new LoginViewImpl();
view.setLoginCallback(new LoginEventHandler());
}

/*
* 初始化view

*/
publicvoid go(HasWidgets container) {
container.clear();
container.add(view.asWidget());
}

// 处理登陆事件
privateclass LoginEventHandler implements EventHandler {

publicvoid handle() {

//调用业务层验证
}

}

}
复制代码

 前文说过MVP只不过是MVC加上约束而已,因此在实现时利用现有的MVC现有的框架,只要让V变成MVP中的V均可以看做是MVP。

MVP在b/s中的实现,以spring MVC为例,实际上当咱们利用spring mvc的 ModelAndView类向view传递数据时,只要view中不包含对m的使用,就能够看做是MVP。只不过在b/s架构之中每次事件都需从新绘制整 个页面,因此这里面隐含全部的视图只包含render接口来绘制全部的部件。

MVP在c/s之中实现,在c/s之中事件到来时只需绘制部分界面,因此这里的V能够提供一个可读性较强的接口。

咱们看看在GWT中如何实现MVP,在GWT之中有个EventBus的概念,EventBus是全局应用事件管理者,注意这里是全局应用事件,这 是指在不一样view中均可以引起的事件。某些事件不需在跨view引起就不要放在EventBus之中。EventBus管理事件和对应的事件的处理程 序。

一般存在AppController类来负责初始化EventBus,并负责控制整个程序的流程。

Presenter类相似controller,负责展现相应的view,并处理view中引起的事件。

view,每一个presenter都有一个自定义的view接口,view都是被动的被presenter使用。

总结,任何模式的核心都是隔离变化,为了隔离变化必须分离职责。因此MVC和MVP从本质都是同样,理解每一个部分的职责才能真正理解MVC和MVP。

相关文章
相关标签/搜索