MVC 模式是設(shè)計(jì)模式中的經(jīng)典模式,它可以有效的分離數(shù)據(jù)層,展示層,和業(yè)務(wù)邏輯層。Web2.0 技術(shù)由于其良好的用戶體驗(yàn)被廣泛應(yīng)用于 WEB 應(yīng)用的展示層。但是在傳統(tǒng)的 web 開(kāi)發(fā)中,展示層的和業(yè)務(wù)邏輯層代碼大量耦合,使得應(yīng)用的可擴(kuò)展性嚴(yán)重降低,同時(shí)頁(yè)面層代碼的可復(fù)用性也很低。本文用實(shí)例介紹,如何使用 dojo toolkit 擴(kuò)展 dojo 的頁(yè)面控件并實(shí)現(xiàn) MVC 模式,有效的分離了展示層與業(yè)務(wù)邏輯層的代碼,同時(shí)使得展示層代碼可復(fù)用性大大提高。
第一部分:Dojo 構(gòu)造 MVC 與傳統(tǒng) MVC 模式的區(qū)別
MVC 模式是"Model-View-Controller"的縮寫(xiě),中文翻譯為"模式 - 視圖 - 控制器"。 基于 MVC 模式的程式一般都是由 Controller, View, Model 這三個(gè)部分組成。Controller 在應(yīng)用程式中主要接受用戶觸發(fā)的事件 (Event),然后 Controller 根據(jù)事先定義好的業(yè)務(wù)邏輯去更新 Model. 在 Model 更新之后,Model 會(huì)通知 (notify) 已注冊(cè)到該模型的視圖(view)進(jìn)行刷新 (refresh) 操作,最后程式將刷新后的視圖展示給用戶。
傳統(tǒng)的 MVC 模式的 model 是一個(gè) javabean,如清單三。而在 DOJO 構(gòu)造的 MVC 當(dāng)中,model 是一個(gè) 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ù)的獲得過(guò)程如下:
JSP — >Servlet — >Service — >DAO — >JavaBean — > 數(shù)據(jù)庫(kù)
或者
JSP — >Servlet — > DAO — >JavaBean — > 數(shù)據(jù)庫(kù)
而在 web2.0 的 MVC 模式中,數(shù)據(jù)的傳輸途徑如下:
JSP — >Javascript — >Servlet — >Service — >DAO — >JavaBean — > 數(shù)據(jù)庫(kù)
或者
JSP — >Javascript — >Servlet — > DAO — >JavaBean — > 數(shù)據(jù)庫(kù)
比如在 DOJO 中,用戶在 JSP 頁(yè)面發(fā)出數(shù)據(jù)請(qǐng)求后,會(huì)先提交給 DOJO 的 Widget 的 Javascript 函數(shù)做處理,這個(gè) widget 調(diào)用相應(yīng)的 servlet,由 servlet 將取到的數(shù)據(jù)轉(zhuǎn)換成 javascript 能夠識(shí)別的 Json 數(shù)據(jù)結(jié)構(gòu),然后這個(gè) widget 根據(jù)自己的刷新規(guī)則,將數(shù)據(jù)填入 widget 的 html template 中,顯示給用戶。
正是因?yàn)閿?shù)據(jù)流向的區(qū)別,導(dǎo)致了傳統(tǒng)的 MVC 的控制層與 web2.0 的控制層有了很大區(qū)別,如果說(shuō)傳統(tǒng) MVC 的控制層是 servlet 的話,那么在 web2.0 中,這個(gè)控制的角色已經(jīng)開(kāi)始由 servlet 轉(zhuǎn)移到了 javascript 中。
在傳統(tǒng)的 MVC 模式中,servlet 負(fù)責(zé)取數(shù)據(jù)和封裝數(shù)據(jù),有時(shí)候也會(huì)包括一些刷新頁(yè)面數(shù)據(jù)的代碼段,而 jsp 負(fù)責(zé)解析數(shù)據(jù),填充數(shù)據(jù)和顯示數(shù)據(jù)??紤]到 JSP 從廣義上來(lái)說(shuō)也是一個(gè) 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 來(lái)完成,解析數(shù)據(jù)和填充數(shù)據(jù)都是在 javascript 中完成。在 dojo 中負(fù)責(zé)解析數(shù)據(jù)和填充數(shù)據(jù)的就是 widget。那么什么是 dojo 的 widget 呢?
什么是 dojo 的 widget ?
Dojo 的 widget 由三部分構(gòu)成,即:
數(shù)據(jù)控制層,一般是 javascript 編寫(xiě)的一個(gè)對(duì)象,它是 dojo widget 的核心,解析數(shù)據(jù)和填充數(shù)據(jù)都是在這里面完成,同時(shí)還可以包含與這個(gè) widget 相關(guān)的一些功能函數(shù),比如隱藏這個(gè) widget,刪除這個(gè) widget 等等。
數(shù)據(jù)顯示層,一般是由 HTML 編寫(xiě)的模板文件,它提供基本的 Widget HTML 視圖。
Css 樣式文件,定義標(biāo)簽的樣式,在 js 代碼或者 HTML 模板文件中使用。
從上面的 dojo widget 的定義可以看出,傳統(tǒng)的 MVC 與 web2.0 也是有很大區(qū)別的,比如在 dojo 中,view 不在是一個(gè) jsp 頁(yè)面,而是由 dojo widget 定義的 template,既由 html 代碼編寫(xiě)的特殊模板。
Dojo 的 widget 由三部分構(gòu)成,即:
數(shù)據(jù)控制層,一個(gè)是 javascript 編寫(xiě)的一個(gè)對(duì)象,它是 dojo widget 的核心,解析數(shù)據(jù)和填充數(shù)據(jù)都是在這里面完成,同時(shí)還可以包含與這個(gè) widget 相關(guān)的一些功能函數(shù),比如隱藏這個(gè) widget,刪除這個(gè) widget 等等。
頁(yè)面顯示層(template),是由 HTML 編寫(xiě)的模板文件,它提供基本的 Widget HTML 視圖。
CSS 樣式文件,定義標(biāo)簽的樣式,在 js 代碼或者 HTML 模板文件中使用。
第二部分 抽象 dojo widget 的共性,實(shí)現(xiàn)可復(fù)用的 MVC
在上一章中,我們列舉了一個(gè) dojo 的 widget 特性,那么我們是否可以對(duì) widget 在進(jìn)一步的提取出共性,提高 widget 的可復(fù)用性,答案是肯定的。
圖 1. dojo 實(shí)現(xiàn) mvc
使用 widget 作為展示層的,可以很好的將頁(yè)面元素很好的封裝和重用。但是在 web 應(yīng)用開(kāi)發(fā)中頁(yè)面展示層往往需要和服務(wù)器端的數(shù)據(jù)進(jìn)行交互,在 web2.0 技術(shù)的支援下,我們可以使用 ajax 將頁(yè)面元素的改變反應(yīng)的服務(wù)器端進(jìn)行處理,然后將返回結(jié)果通過(guò)在頁(yè)面中預(yù)先定義的回調(diào)函數(shù)進(jìn)行性展示。然后大量的回調(diào)函數(shù)將會(huì)破壞展示層的良好的封裝。使得代碼晦澀難懂。因此我們需要在 web2.0 應(yīng)用中實(shí)現(xiàn) MVC 模式,將模型改變,以及視圖的自動(dòng)刷新進(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)模型改變時(shí),模型可以調(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 來(lái)進(jìn)行與視圖的通信。registerView 函數(shù)可以讓 model 綁定一個(gè)視圖,unRegisterView 函數(shù)可以讓 model 解綁定一個(gè)視圖,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 類(lèi),主要負(fù)責(zé)與服務(wù)器端的 servlet 通信。獲取服務(wù)器端的數(shù)據(jù)更新,并將更新后的數(shù)據(jù)通知到頁(yè)面模型層。
使 widget 繼承 view. 很簡(jiǎn)單,只需要在 declare 中申明該 widget 繼承與 view 就可以了。
清單 15. updateView.
taas.layout.LinkPane.prototype.updateView = function(json) { alert(“this view has been updated”); } |
清單中的代碼實(shí)現(xiàn)了自定義的視圖刷新規(guī)則 updateView,該函數(shù)被動(dòng)態(tài)綁定到 LinkPane widget 對(duì)象中。
清單 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 抽象對(duì)象中派生出 ProjectList 對(duì)象模型以供 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è)我們?cè)陧?yè)面中使用了一個(gè) button,button 的 onclick 事件調(diào)用 controller 的 remoteUpdate 方法與 Model 中對(duì)應(yīng)的 servlet 通訊。
圖 2 程序執(zhí)行過(guò)程。Sequence 圖。
第三部分 使用 dojo 的 subscribe 和 publish 方式簡(jiǎn)化代碼
由于 dojo1.2 版本已經(jīng)提供 subscribe/publish 消息通知機(jī)制,所以可以將 model 與 view 的關(guān)系使用 subscribe/publish 機(jī)制來(lái)簡(jiǎn)化。簡(jiǎn)化后 model 被 subscribe/publish 機(jī)制中的 topic 代替 .
下面為簡(jiǎn)化后的代碼。
清單 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)的請(qǐng)求參數(shù)發(fā)給遠(yuǎn)程 servlet。遠(yuǎn)程 servlet 收到請(qǐng)求后進(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 頁(yè)面的展示如何同步。
- 訪問(wèn) Dojo的官方站點(diǎn),關(guān)于 Dojo 的最權(quán)威的站點(diǎn)。
- “教程:使用Dojo開(kāi)發(fā)HTML小部件”(developerWorks,2006 年 12 月):您將學(xué)到使用 Dojo 開(kāi)發(fā) HTML 小部件的基礎(chǔ)知識(shí);包括如何引用一個(gè)圖像、如何向 HTML 頁(yè)面中添加事件處理程序以及如何處理復(fù)合小部件。
- “評(píng)論專(zhuān)欄: Scott Johnson:沉迷于Dojo”(developerWorks,2008 年 4 月):Dojo 內(nèi)部人員討論 Dojo Toolkit 如此廣受歡迎而成為必備下載工具的原因、其現(xiàn)狀和未來(lái)。
- “基于Dojo的本地化開(kāi)發(fā)”(developerWorks,2008 年 1 月):本文介紹了基于 Dojo 的本地化的實(shí)現(xiàn),通過(guò)實(shí)例講解了如何利用 Dojo 提供的本地化支持模塊來(lái)實(shí)現(xiàn)軟件的本地化。
- “提高基于Dojo的Web 2.0應(yīng)用程序的性能”(developerWorks,2008 年 2 月):本文通過(guò)演示一些實(shí)用的技巧來(lái)提高 Dojo 的性能,幫助開(kāi)發(fā)人員找出 Web 2.0 應(yīng)用程序的性能瓶頸。
- “使用Dojo開(kāi)發(fā)支持Accessibility的Web應(yīng)用”(developerWorks,2008 年 5 月):幫助開(kāi)發(fā)人員了解 Accessibility 的基本內(nèi)容,掌握 Dojo 開(kāi)發(fā)可訪問(wèn)性 Web 應(yīng)用的基本技能。
- “使用Dojo國(guó)際化Web應(yīng)用程序”(developerWorks,2008 年 8 月):通過(guò)本文獲得有關(guān)如何使用 Dojo 這個(gè)重要特性的簡(jiǎn)短的指導(dǎo)。
- “用Firebug動(dòng)態(tài)調(diào)試和優(yōu)化應(yīng)用程序”(developerWorks,2008 年 5 月):了解如何使用 Firefox 瀏覽器的免費(fèi)開(kāi)源擴(kuò)展 Firebug,它提供了很多有用的開(kāi)發(fā)特性和工具。
- Ajax資源中心:developerWorks 上所有有關(guān) Ajax 的問(wèn)題都可以在這里找到解答。