深刻理解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。

相關文章
相關標籤/搜索