Prototype框架提供了基於JavaScript語言的面向對象風格的AJAX庫,使編寫動態WEB程序成爲可能。基於Prototype的Scriptaculous的流行就是一個很好的證明。<o:p></o:p>
<o:p></o:p>Prototype封裝了Form、Element和Event,併爲Form提供了Observer模式以便於進行事件管理和減少依賴。<o:p></o:p>
問題:<o:p></o:p>
<o:p></o:p>Prototype設計了兩種Observer,一種基於Timeout,一種基於事件。但無法支持普通對象級別的Observer。例如在調用A對象的X 方法之後觸發B對象的Y方法。本文將嘗試一種在對象級別實現Observer的方式。<o:p></o:p>
分析:<o:p></o:p><o:p></o:p>Observer模式實現在“被觀察者”的行爲發生變化時,向多個對象(觀察者)發生消息(並由該對象完成Update操作)。面嚮對象語言藉助Interface實現Observer模式。如Java中的java.util.Observable相當於Subject(被觀察者),而java.util.Observer接口的實現者(觀察者)完成Update操作。
上述方式對於降低類之間依賴不錯。但是我們必須從特定的類繼承或實現特定的接口。Observer消息觸發方式導致問題也需要考慮。詳見:http://www.microsoft.com/china/MSDN/library/architecture/patterns/esp/DesObserver.mspx?mfr=true<o:p></o:p>
當然,我們可以在JavaScript中按照上述結構實現Observer會導致更多問題,例如如何實現接口編程?即使這個問題很容易解決,無論從理解和使用上這種模式都不太方便、不夠直接。<o:p></o:p>
設計:<o:p></o:p>
再次聚焦Observer模式並觀察它的核心,會發現如何實現消息觸發是關鍵。一旦ConcreteSubject對象擁有ConcreteObserver的引用,在消息觸發時就可以直接調用後者的Update方法。<o:p></o:p>
<o:p></o:p>
代碼執行順序:<o:p></o:p>
在concreteSubject的對象內部:<o:p></o:p>
if(this.stateChanged())<o:p></o:p>
concreteObservers.update(this,args);<o:p></o:p>
再看我的問題:在調用A對象X方法之後調用B對象的Y方法。<o:p></o:p>
在A對象內部:<o:p></o:p>
this.X();<o:p></o:p>
B.Y();<o:p></o:p>
<o:p></o:p>動態語言特性支持在Runtime時刻改變對象(數據和行爲)。考慮引入對象Observer及方法register。用戶期望A對象的X方法調用後觸發B對象Y方法時,將A對象、X方法名稱和期望的動作(B.Y)傳入register方法,然後在該方法中改寫X的行爲。<o:p></o:p>
A. _$_X = A.X;<o:p></o:p>
A.X = {this._$_X(); B.Y();}<o:p></o:p>
<o:p></o:p>
同時增加unregister方法設法恢復A對象的行爲(X方法)。<o:p></o:p>
<o:p> </o:p>
代碼:<o:p> </o:p>
- var StringBuffer = Class.create();
- StringBuffer.prototype = {
- emptyString:"",
- initialize:function(){
- this.data=[];
- },
- clear:function(){
- this.data.length=0;
- },
- append:function(){
- for(var i=0,len=arguments.length;i
- this.data[this.data.length]=arguments[i];
- }
- return this;
- },
- toString:function(){
- return this.data.join(this.emptyString);
- }
- };
- var Observer = Class.create();
- Observer.prototype = {
- initialize:function(){
- },
- /**
- * register the action to the object's method
- * @param {Object} object
- * @param {String} method
- * @param {String} action
- */
- register:function(object,method,action){
- if(!object || !method || !action || method == typeof String || action == typeof String)return;
- //checks whether the method is existed.
- var f = object[method];
- if(!f)return;
- //copy, rename the method and replace it as a new method.
- var _$_f = "_$_"+method;
- if(!object[_$_f]){
- object[_$_f] = f;
- object[method]= this.createFunction(method,action);
- }
- },
- /**
- * unregister the action from the object's method
- * @param {Object} object
- * @param {Object} method
- */
- unregister:function(object,method){
- if(!object||!method)return;
- var _$_f = "_$_"+method;
- if(object[_$_f]){
- object[method] = null;
- object[method] = object[_$_f];
- object[_$_f] = null;
- }
- },
- /**
- * create a new function that invokes both the method and the action
- * @param {String} method
- * @param {String} action
- */
- createFunction:function(method,action){
- var sb = new StringBuffer();
- sb.append("this[\"_$_",method,"\"]();\r",action);
- return new Function(sb.toString());
- }
- }