MVC 模式是設(shè)計(jì)模式中的經(jīng)典模式,它可以有效的分離數(shù)據(jù)層,展示層,和業(yè)務(wù)邏輯層。Web2.0 技術(shù)由于其良好的用戶體驗(yàn)被廣泛應(yīng)用于 WEB 應(yīng)用的展示層。但是在傳統(tǒng)的 web 開發(fā)中,展示層的和業(yè)務(wù)邏輯層代碼大量耦合,使得應(yīng)用的可擴(kuò)展性嚴(yán)重降低,同時頁面層代碼的可復(fù)用性也很低。本文用實(shí)例介紹,如何使用 dojo toolkit 擴(kuò)展 dojo 的頁面控件并實(shí)現(xiàn) MVC 模式,有效的分離了展示層與業(yè)務(wù)邏輯層的代碼,同時使得展示層代碼可復(fù)用性大大提高。
第一部分:Dojo 構(gòu)造 MVC 與傳統(tǒng) MVC 模式的區(qū)別
MVC 模式是"Model-View-Controller"的縮寫,中文翻譯為"模式 - 視圖 - 控制器"。 基于 MVC 模式的程式一般都是由 Controller, View, Model 這三個部分組成。Controller 在應(yīng)用程式中主要接受用戶觸發(fā)的事件 (Event),然后 Controller 根據(jù)事先定義好的業(yè)務(wù)邏輯去更新 Model. 在 Model 更新之后,Model 會通知 (notify) 已注冊到該模型的視圖(view)進(jìn)行刷新 (refresh) 操作,最后程式將刷新后的視圖展示給用戶。
傳統(tǒng)的 MVC 模式的 model 是一個 javabean,如清單三。而在 DOJO 構(gòu)造的 MVC 當(dāng)中,model 是一個 json 的數(shù)據(jù)結(jié)構(gòu),封裝完,返回的數(shù)據(jù)結(jié)構(gòu)形式如下:
{“books”:[{“bookName:The art of programming”,”price:90”}, {“bookName:MVC introduction”,”price:90”}]} |
在傳統(tǒng)的 MVC 模式中,數(shù)據(jù)的獲得過程如下:
JSP — >Servlet — >Service — >DAO — >JavaBean — > 數(shù)據(jù)庫
或者
JSP — >Servlet — > DAO — >JavaBean — > 數(shù)據(jù)庫
而在 web2.0 的 MVC 模式中,數(shù)據(jù)的傳輸途徑如下:
JSP — >Javascript — >Servlet — >Service — >DAO — >JavaBean — > 數(shù)據(jù)庫
或者
JSP — >Javascript — >Servlet — > DAO — >JavaBean — > 數(shù)據(jù)庫
比如在 DOJO 中,用戶在 JSP 頁面發(fā)出數(shù)據(jù)請求后,會先提交給 DOJO 的 Widget 的 Javascript 函數(shù)做處理,這個 widget 調(diào)用相應(yīng)的 servlet,由 servlet 將取到的數(shù)據(jù)轉(zhuǎn)換成 javascript 能夠識別的 Json 數(shù)據(jù)結(jié)構(gòu),然后這個 widget 根據(jù)自己的刷新規(guī)則,將數(shù)據(jù)填入 widget 的 html template 中,顯示給用戶。
正是因?yàn)閿?shù)據(jù)流向的區(qū)別,導(dǎo)致了傳統(tǒng)的 MVC 的控制層與 web2.0 的控制層有了很大區(qū)別,如果說傳統(tǒng) MVC 的控制層是 servlet 的話,那么在 web2.0 中,這個控制的角色已經(jīng)開始由 servlet 轉(zhuǎn)移到了 javascript 中。
在傳統(tǒng)的 MVC 模式中,servlet 負(fù)責(zé)取數(shù)據(jù)和封裝數(shù)據(jù),有時候也會包括一些刷新頁面數(shù)據(jù)的代碼段,而 jsp 負(fù)責(zé)解析數(shù)據(jù),填充數(shù)據(jù)和顯示數(shù)據(jù)??紤]到 JSP 從廣義上來說也是一個 servlet,所以 servlet 就包含了從取數(shù)據(jù)、封裝數(shù)據(jù)、解析數(shù)據(jù)、填充數(shù)據(jù)和顯示數(shù)據(jù)的一條龍服務(wù)。
而在 web2.0 中,servlet 仍然負(fù)責(zé)取數(shù)據(jù)和封裝數(shù)據(jù),但是解析數(shù)據(jù)、填充數(shù)據(jù)和顯示數(shù)據(jù)已經(jīng)不再由 JSP 來完成,解析數(shù)據(jù)和填充數(shù)據(jù)都是在 javascript 中完成。在 dojo 中負(fù)責(zé)解析數(shù)據(jù)和填充數(shù)據(jù)的就是 widget。那么什么是 dojo 的 widget 呢?
什么是 dojo 的 widget ?
Dojo 的 widget 由三部分構(gòu)成,即:
數(shù)據(jù)控制層,一般是 javascript 編寫的一個對象,它是 dojo widget 的核心,解析數(shù)據(jù)和填充數(shù)據(jù)都是在這里面完成,同時還可以包含與這個 widget 相關(guān)的一些功能函數(shù),比如隱藏這個 widget,刪除這個 widget 等等。
數(shù)據(jù)顯示層,一般是由 HTML 編寫的模板文件,它提供基本的 Widget HTML 視圖。
Css 樣式文件,定義標(biāo)簽的樣式,在 js 代碼或者 HTML 模板文件中使用。
從上面的 dojo widget 的定義可以看出,傳統(tǒng)的 MVC 與 web2.0 也是有很大區(qū)別的,比如在 dojo 中,view 不在是一個 jsp 頁面,而是由 dojo widget 定義的 template,既由 html 代碼編寫的特殊模板。
Dojo 的 widget 由三部分構(gòu)成,即:
數(shù)據(jù)控制層,一個是 javascript 編寫的一個對象,它是 dojo widget 的核心,解析數(shù)據(jù)和填充數(shù)據(jù)都是在這里面完成,同時還可以包含與這個 widget 相關(guān)的一些功能函數(shù),比如隱藏這個 widget,刪除這個 widget 等等。
頁面顯示層(template),是由 HTML 編寫的模板文件,它提供基本的 Widget HTML 視圖。
CSS 樣式文件,定義標(biāo)簽的樣式,在 js 代碼或者 HTML 模板文件中使用。
第二部分 抽象 dojo widget 的共性,實(shí)現(xiàn)可復(fù)用的 MVC
在上一章中,我們列舉了一個 dojo 的 widget 特性,那么我們是否可以對 widget 在進(jìn)一步的提取出共性,提高 widget 的可復(fù)用性,答案是肯定的。
圖 1. dojo 實(shí)現(xiàn) mvc
使用 widget 作為展示層的,可以很好的將頁面元素很好的封裝和重用。但是在 web 應(yīng)用開發(fā)中頁面展示層往往需要和服務(wù)器端的數(shù)據(jù)進(jìn)行交互,在 web2.0 技術(shù)的支援下,我們可以使用 ajax 將頁面元素的改變反應(yīng)的服務(wù)器端進(jìn)行處理,然后將返回結(jié)果通過在頁面中預(yù)先定義的回調(diào)函數(shù)進(jìn)行性展示。然后大量的回調(diào)函數(shù)將會破壞展示層的良好的封裝。使得代碼晦澀難懂。因此我們需要在 web2.0 應(yīng)用中實(shí)現(xiàn) MVC 模式,將模型改變,以及視圖的自動刷新進(jìn)行封裝,已取得更好的復(fù)用性。
清單 11. VIEW.js
if (!dojo._hasResource["taas._base.View"]) { dojo._hasResource["taas._base.View"] = true; dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); dojo.provide("taas._base.View"); dojo.declare("taas._base.View",null,{ _model:null, _taasSrcPath:dojo.moduleUrl("taas",""), responseObject:null, refresh:function(object){ this.responseObject = object; if(this.updateView!=undefined&&typeof this.updateView=="function"){ this.updateView(this.responseObject); } }, _bindModel:function(dataModel){ this._model = dataModel; } }); } |
清單中的程序 定義了抽象的 view. 其中 _model 為為抽象 view 所關(guān)聯(lián)的數(shù)據(jù)模型。
bindModel 方法將視圖與數(shù)據(jù)模型進(jìn)行關(guān)聯(lián)。Refresh 方法提供當(dāng)模型改變時,模型可以調(diào)用的用于刷新視圖的方法。在 Refresh 函數(shù)中判斷是否存在 updateView 函數(shù)如果存在就調(diào)用該函數(shù)。updateView 函數(shù)用于用于自定義的視圖如何進(jìn)行刷新。
清單 12. MODEL.js
if (!dojo._hasResource["taas._base.DataModel"]) { dojo._hasResource["taas._base.DataModel"] = true; dojo.provide("taas._base.DataModel"); dojo.declare("taas._base.DataModel",null,{ _views:null, uri:null, constructor : function(uri){ this._views = new Array(); this.uri = uri; }, registerView:function(view){ view.bindModel(this); this._views.push(view); }, unRegisterView : function (view){ var i = this._views.indexOf(view); if(i > 0) this._views.slice(i,1); }, notifyViews : function (json){ for(var i = 0; i < this._views.length; i++) { this._views[i].refresh(json); } } }); } |
清單中代碼為抽象的 model. 它使用了 registerView,unRegisterView notifyViews 來進(jìn)行與視圖的通信。registerView 函數(shù)可以讓 model 綁定一個視圖,unRegisterView 函數(shù)可以讓 model 解綁定一個視圖,notifyViews 函數(shù),用于通知該模型所綁定的所有視圖進(jìn)行刷新。
清單 13. Controller.js
if (!dojo._hasResource["taas._base.Controller2"]) { dojo._hasResource["taas._base.Controller2"] = true; dojo.provide("taas._base.Controller2"); dojo.declare("taas._base.Controller2",null,{ }); taas._base.Controller2.remoteUpdate = function (dataModelUri,topic,formId){ dojo.info("taas._base.Controller2 deprecated, use taas._base.Controller instead, 1.0") _topic = topic; _dataModelUri = dataModelUri; _formId = formId; _form = dojo.byId(_formId); var doResponse = function (responseText){ dojo.publish(_topic,[responseText]); }; dojo.xhrGet({ url: _dataModelUri, preventCache: true, form:_form, handleAs: "text", method:"get", load: doResponse }); } } |
清單中的代碼為 Controller 類,主要負(fù)責(zé)與服務(wù)器端的 servlet 通信。獲取服務(wù)器端的數(shù)據(jù)更新,并將更新后的數(shù)據(jù)通知到頁面模型層。
使 widget 繼承 view. 很簡單,只需要在 declare 中申明該 widget 繼承與 view 就可以了。
清單 15. updateView.
taas.layout.LinkPane.prototype.updateView = function(json) { alert(“this view has been updated”); } |
清單中的代碼實(shí)現(xiàn)了自定義的視圖刷新規(guī)則 updateView,該函數(shù)被動態(tài)綁定到 LinkPane widget 對象中。
清單 16. ProjectList 模型 .
function ProjectList(uri) { this.uri = uri; } ProjectList.prototype = new taas._base.DataModel(this.uri); Var projectListModel = new ProjectList(“http://localhost:8080/servlet/ProjectManagement”) |
清單中的代碼從 DataModel 抽象對象中派生出 ProjectList 對象模型以供 LinkPane 使用。
清單 17. 模型與視圖綁定 .
var myLinkPane = new taas.layout.LinkPane({},”linkpane01”). projectListModel.registerView(myLinkPane); |
清單 18. controller 與遠(yuǎn)程 servlet 通信 .
<input type="button" onclick="taas._base.Controller.remoteUpdate(ProjectListModel, { 'action' : 'listall' })"> </input> |
清單中代碼假設(shè)我們在頁面中使用了一個 button,button 的 onclick 事件調(diào)用 controller 的 remoteUpdate 方法與 Model 中對應(yīng)的 servlet 通訊。
圖 2 程序執(zhí)行過程。Sequence 圖。
第三部分 使用 dojo 的 subscribe 和 publish 方式簡化代碼
由于 dojo1.2 版本已經(jīng)提供 subscribe/publish 消息通知機(jī)制,所以可以將 model 與 view 的關(guān)系使用 subscribe/publish 機(jī)制來簡化。簡化后 model 被 subscribe/publish 機(jī)制中的 topic 代替 .
下面為簡化后的代碼。
清單 19. 修改后的 VIEW.js
if (!dojo._hasResource["taas._base.View"]) { dojo._hasResource["taas._base.View"] = true; dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); dojo.provide("taas._base.View"); dojo.declare("taas._base.View",null,{ _model:null, topic:"", _taasSrcPath:dojo.moduleUrl("taas",""), responseObject:null, refresh:function(object){ this.responseObject = object; if(this.updateView!=undefined&&typeof this.updateView=="function"){ this.updateView(this.responseObject); } }, bindModel:function(dataModel){ this._model = dataModel; }, _bindTopic:function(){ if(this.topic!=undefined&&this.topic!=""){ dojo.subscribe(this.topic,this,"refresh"); } } }); } |
清單 21. 修改后的 Controller.js
if (!dojo._hasResource["taas._base.Controller"]) { dojo._hasResource["taas._base.Controller"] = true; dojo.provide("taas._base.Controller"); dojo.declare("taas._base.Controller",null,{ }); taas._base.Controller.remoteUpdate = function (dataModelUri,topic,requestContent){ _topic = topic; _dataModelUri = dataModelUri; _requestContent = requestContent; console.debug(_requestContent); var doResponse = function (responseText){ dojo.forEach(_topic,function(item){ var jsonObj = dojo.fromJson(responseText)[item]; //console.debug(dojo.toJson(jsonObj)); dojo.publish(item,[dojo.toJson(jsonObj)]); }); }; var getFormJson = function() { dojo.xhrGet({ url: _dataModelUri, preventCache: true, content:_requestContent, handleAs: "text", method:"get", load: doResponse }); }; var getFromForm = function() { dojo.xhrGet({ url: _dataModelUri, preventCache: true, form:_requestContent, handleAs: "text", method:"get", load: doResponse }); }; var doRequest = function () { if(dojo.isObject(_requestContent)) { getFormJson(); } else if(dojo.isString(_requestContent)){ getFromForm(); } }; doRequest(); } } |
<input type="button" onclick='taas._base.Controller.remoteUpdate ( 'http://localhost:8080/servlet/ProjectManagement ' , ["projects"], { 'action' : 'querybyuserid' });” |
清單中代碼中 onclick 屬性調(diào)用了 Controller 的 remoteUpdate 方法,該方法將調(diào)用遠(yuǎn)程 servlet 的 doGet/doPost 方法,并將相應(yīng)的請求參數(shù)發(fā)給遠(yuǎn)程 servlet。遠(yuǎn)程 servlet 收到請求后進(jìn)行相應(yīng)的業(yè)務(wù)邏輯處理,最后將處理結(jié)果返回到 controller 的回調(diào)函數(shù)執(zhí)行從而刷新視圖。
使用 web2.0 的 MVC 模式,使我們能更加關(guān)于與業(yè)務(wù)邏輯的實(shí)現(xiàn),而不用糾纏與服務(wù)器端數(shù)據(jù)模型與 web 頁面的展示如何同步。
- 訪問 Dojo的官方站點(diǎn),關(guān)于 Dojo 的最權(quán)威的站點(diǎn)。
- “教程:使用Dojo開發(fā)HTML小部件”(developerWorks,2006 年 12 月):您將學(xué)到使用 Dojo 開發(fā) HTML 小部件的基礎(chǔ)知識;包括如何引用一個圖像、如何向 HTML 頁面中添加事件處理程序以及如何處理復(fù)合小部件。
- “評論專欄: Scott Johnson:沉迷于Dojo”(developerWorks,2008 年 4 月):Dojo 內(nèi)部人員討論 Dojo Toolkit 如此廣受歡迎而成為必備下載工具的原因、其現(xiàn)狀和未來。
- “基于Dojo的本地化開發(fā)”(developerWorks,2008 年 1 月):本文介紹了基于 Dojo 的本地化的實(shí)現(xiàn),通過實(shí)例講解了如何利用 Dojo 提供的本地化支持模塊來實(shí)現(xiàn)軟件的本地化。
- “提高基于Dojo的Web 2.0應(yīng)用程序的性能”(developerWorks,2008 年 2 月):本文通過演示一些實(shí)用的技巧來提高 Dojo 的性能,幫助開發(fā)人員找出 Web 2.0 應(yīng)用程序的性能瓶頸。
- “使用Dojo開發(fā)支持Accessibility的Web應(yīng)用”(developerWorks,2008 年 5 月):幫助開發(fā)人員了解 Accessibility 的基本內(nèi)容,掌握 Dojo 開發(fā)可訪問性 Web 應(yīng)用的基本技能。
- “使用Dojo國際化Web應(yīng)用程序”(developerWorks,2008 年 8 月):通過本文獲得有關(guān)如何使用 Dojo 這個重要特性的簡短的指導(dǎo)。
- “用Firebug動態(tài)調(diào)試和優(yōu)化應(yīng)用程序”(developerWorks,2008 年 5 月):了解如何使用 Firefox 瀏覽器的免費(fèi)開源擴(kuò)展 Firebug,它提供了很多有用的開發(fā)特性和工具。
- Ajax資源中心:developerWorks 上所有有關(guān) Ajax 的問題都可以在這里找到解答。