[修練之路-JS]3. JS’s Observer pattern (using prototype)
參考文件:
Essential JavaScript And jQuery Design Patterns – A Free New Book,
observer pattern concept:http://www.dofactory.com/Patterns/PatternObserver.aspx#_self2
super.func():http://phrogz.net/js/classes/OOPinJS2.html
prototype:http://msdn.microsoft.com/zh-tw/scriptjunkie/gg476048.aspx
觀察者樣式是重要性/實用性很高的樣式, 本系列文件會分3篇來demo
第一篇. 使用prototype實現物件導向設計下的觀察者樣式.
第二篇. 使用closure實現JavaScript強大語法下的觀察者樣式.
第三篇. 使用jQuery實現方便我們實作的觀察者樣式.
觀察者樣式簡單的舉個例子, 像是我們平常在寫畫面程式時, 增加方法去觀察使用者的按鍵/滑鼠行為後做處理, 即為一種常見的用法;
在觀察者樣式中, 有兩個重要的Type:
1. 主題(Subject)
2. 觀察者(Observer)
三個重要的Operator
1. 訂閱(Subscribe)
2. 取消訂閱(Unsubscribe)
3. 通知更新(Notify/Publish/Update …)
以類別圖來解說此回的案例,
主題為位置(Location) 即為 x , y ,
而觀察者(CodeGenerator)則是要依 x , y 產生出代碼(Code),
另外增加了參數 z 來豐富產生代碼的觀察者(TimeCodeGenerator/RandomCodeGenerator),
在畫面上的主程式則是由LocationTracker來處理滑鼠移動時的x , y值.
在循序圖我們可以簡要的看到樣式的經過
1. 畫面的主程式建立位置(Location)物件
2-5 觀察者(CodeGenerator)訂閱位置.
6. 當位置變更時通知所有的觀察者建立其代碼
7. 取消訂閱
在案例中, 我們要做的如下圖, 當啟動時, 在空白處滑動來產生2種不同的代碼, 按下Start It 來啟動, 或按下End It結束.
因為有用到jshashtable.js 故記得要先去下載, 詳細的解說都在程式的註解中, 可以直接RUN 的版本在 http://jsfiddle.net/hectorlee369/Vmcjs/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<script type="text/javascript" src="jshashtable.js"></script>
<script type="text/javascript">
// begin Subject ========
// 主題界面, 訂義訂閱(subscribe), 取消訂閱(unsubscribe), 發佈(notify/publish)
var Subject = function(){ this.initialize(); }
Subject.prototype.initialize=function(){
return null;
}
Subject.prototype.subscribe=function(observer){
return null;
}
Subject.prototype.unsubscribe=function(id){
return null;
}
Subject.prototype.notify=function(args){
return null;
}
// end Subject ========
// begin Location ========
// 位置界面, 實作訂閱(subscribe), 取消訂閱(unsubscribe), 發佈(notify/publish)
var Location = function(){ this.initialize(); }
// 繼承
Location.prototype = new Subject();
// implement 以Hashtable 當作訂閱的儲存集合
Location.prototype.initialize=function(){
this.observers = new Hashtable();
this.current_id = 0; // 遞增給每一個訂閱者當做id
}
// implement
Location.prototype.unsubscribe=function(id){
this.observers.remove(id);
}
// implement 加入到訂閱集合中
Location.prototype.subscribe=function(observer){
this.current_id++ ;
this.observers.put( this.current_id, observer );
return this.current_id;
}
// implement 通知每一個訂閱者
Subject.prototype.notify=function(x, y){
this.observers.each(function(key, value){
value.generate({ x : x, y : y });
});
}
// end Location ========
// begin Observer ========
// 觀察者界面, 訂義製作(generate)
var Observer = function() { this.initialize(); }
Observer.prototype.initialize=function(){
return null;
}
Observer.prototype.generate=function(args){
return null;
}
// end Observer ========
// begin CodeGenerator ========
// 觀察者-代碼建立, 觀察Location(x, y)值後建立代碼
var CodeGenerator = function() { this.initialize(); }
// 繼承
CodeGenerator.prototype = new Observer();
// override, 增加類別屬性: code
CodeGenerator.prototype.initialize=function(){
this.code = "";
}
// override , 增加參數: [args[x, y,max_length, get_z, ctrl_id]]
// max_length: <int> 代碼長度
// get_attr_num: <function> 子類別提供代碼產生之數值參數
// ctrl_id: html dom object id
// 依參數 (x, y , get_z) 運算產生一個A-Z的字元, 並顯示於畫面中.
CodeGenerator.prototype.generate=function(args){
if(this.code.length< args.max_length + 1 ){
var c = String.fromCharCode(65 + (args.x + args.y + args.get_z()) % 26);
this.code+=c;
document.getElementById(args.ctrl_id).innerText = this.code + " [xxx 未完成 xxx]";
}else{
document.getElementById(args.ctrl_id).innerText = this.code + " [ooo 已完成 ooo]";
}
}
// end CodeGenerator ========
// begin TimeCodeGenerator ========
// 觀察者-依時間為參數產生代碼
var TimeCodeGenerator = function() { this.initialize(); }
// 繼承
TimeCodeGenerator.prototype = new CodeGenerator();
// override
TimeCodeGenerator.prototype.generate=function(args){
var get_z=function(){
return new Date().getTime();
}
// 呼叫父類別方法, 類似super.xxx() or base.xxx()
CodeGenerator.prototype.generate.call(this, { x:args.x, y:args.y, ctrl_id : 'time_gener', max_length : 32, get_z: get_z });
}
// end TimeCodeGenerator ========
// begin RandomCodeGenerator ========
// 觀察者-依亂數為參數產生代碼
var RandomCodeGenerator = function() { this.initialize(); }
// 繼承
RandomCodeGenerator.prototype = new CodeGenerator();
// override
RandomCodeGenerator.prototype.generate=function(args){
var get_z=function(){
return Math.random() * 100000 ;
}
// 呼叫父類別方法, 類似super.xxx() or base.xxx()
CodeGenerator.prototype.generate.call(this, { x: args.x, y:args.y, ctrl_id : 'random_gener', max_length : 16, get_z: get_z });
}
// end RandomCodeGenerator ========
// begin LocationTracker
// 處理畫面功能的啟動/關閉
var LocationTracker = function(){ this.initialize(); }
LocationTracker.prototype.initialize=function(){
this.subject = new Location();
this.subjectId_01;
this.subjectId_02;
this.status=0;
}
LocationTracker.prototype.start=function(){
this.end();
this.subjectId_01 = this.subject.subscribe( new TimeCodeGenerator() ) ;
this.subjectId_02 = this.subject.subscribe( new RandomCodeGenerator() ) ;
this.status=1;
document.getElementById('holder').style.display='block';
}
LocationTracker.prototype.end=function(){
if(this.subjectId_01){
this.subject.unsubscribe(this.subjectId_01);
this.subjectId_01=null;
document.getElementById("time_gener").innerText = "";
}
if(this.subjectId_02){
this.subject.unsubscribe(this.subjectId_02);
this.subjectId_02=null;
document.getElementById("random_gener").innerText = "";
}
this.status=0;
document.getElementById('holder').style.display='none';
}
var tracker = new LocationTracker();
</script>
</head>
<body onload="tracker.start()">
<input type="button" value="Start It" onclick="tracker.start();" />
<input type="button" value="End It" onclick="tracker.end();" /> <p />
<div id="holder">
請在空白處滑動你的mouse來產生代碼:
<div style="height:300px; width:300px; border-width:1px; border-style:solid" onmousemove="if(tracker.status==1) tracker.subject.notify(event.x, event.y)"></div>
<p>TimeCodeGenerator: <span id="time_gener" ></span> </p>
<p>RandomCodeGenerator: <span id="random_gener" ></span> </p>
</div>
</body>
</html>
印象中不知道在那一份JavaScript牛人的文件中有寫到, 這種寫法就是寫JAVA 或 C#物件導向語言走火入魔的方式, 即不美觀又笨重肥大也不... , 總之被嫌到爛, 可是以我有限的腦神經, 其實最看得懂這種Coding style, 所以把它放在第一篇. 下回就要挑戰即美觀又輕巧的closure ~~