[修練之路-JS]4. JS’s Observer pattern (using closure)

[修練之路-JS]4. JS’s Observer pattern (using closure)

續前一章"[修練之路-JS]3. JS’s Observer pattern (using prototype)"

並運用"[修練之路-JS]2. JS and jQuery's Module Design Pattern"

 

主要差別在於語法不同, 另外因為用了Module Design Pattern故會變成有點像是static的用法, 而非new一個object.

 

<!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(){
    return {
        subscribe : null,
        unsubscribe : null,
        notify : null
    };
}());
// end Subject ========    
// begin Location ========
// 位置類別, 實作訂閱(subscribe), 取消訂閱(unsubscribe), 發佈(notify/publish)
var Location = (function(s){
    var l = {};
    
    var observers = new Hashtable();
    var current_id=0;
    
    var subscribe=function(observer){        
        current_id++ ;
        observers.put( current_id, observer );        
        return current_id;
    };
    var unsubscribe=function(id){
        observers.remove(id);
    };
    var notify=function(x, y){
        observers.each(function(key, func){ 
            func.generate({ x : x, y : y }); 
        });
    };
    
    l.subscribe = subscribe;
    l.unsubscribe = unsubscribe;
    l.notify = notify;    
    return l;
}(Subject));
// end Location ========
// begin Observer ========
// 觀察者界面, 訂義製作(generate)
var Observer = (function(){ 
    return {
        generate: null
    };
}());
// end Observer ========    
// begin CodeGenerator ========
// 觀察者-代碼建立, 觀察Location(x, y)值後建立代碼
var CodeGenerator = (function(o){
    var c = {};
    // 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的字元, 並顯示於畫面中.
    var generate = function(args){
        if(this.code.length < this.max_length + 1 ){            
            var c = String.fromCharCode(65 + (args.x + args.y + this.get_z()) % 26);
            this.code+=c;        
            document.getElementById(this.ctrl_id).innerText = this.code + "  [xxx 未完成 xxx]";
        }else{
            document.getElementById(this.ctrl_id).innerText = this.code + "  [ooo 已完成 ooo]";
        }            
    }
    
    var clear = function(){
        document.getElementById(this.ctrl_id).innerText = "";
        this.code = "";
    }
    
    c.generate=generate;
    c.code="";
    c.max_length = 8;
    c.get_z = function(){ return 0 ;};
    c.ctrl_id = "";
    c.clear = clear;
    return c;
}(Observer));
// end CodeGenerator ========
 
// begin TimeCodeGenerator ========
// 觀察者-依時間為參數產生代碼
var TimeCodeGenerator = (function(c){
    // 繼承
    var t = {}, key;
    for( key in c ){
        if(c.hasOwnProperty(key)){
            t[key] = c[key];
        }
    }
 
    var get_z=function(){
            return new Date().getTime();
        };
    
    t.max_length=32;
    t.get_z=get_z;
    t.ctrl_id = "time_gener";
    return t;
}(CodeGenerator));
// end TimeCodeGenerator ========
 
// begin RandomCodeGenerator ========
// 觀察者-依亂數為參數產生代碼
var RandomCodeGenerator = (function(c){
    // 繼承
    var r = {}, key;
    for( key in c ){
        if(c.hasOwnProperty(key)){
            r[key] = c[key];
        }
    }
    var get_z=function(){
            return Math.random() * 100000 ;
        };
    
    r.max_length=16;
    r.get_z=get_z;
    r.ctrl_id = "random_gener";
    return r;        
}(CodeGenerator));
// end RandomCodeGenerator ========
 
// begin LocationTracker
// 處理畫面功能的啟動/關閉
var LocationTracker = (function(){
    var start=function(){        
        end();
        this.subjectId_01 = this.subject.subscribe( TimeCodeGenerator ) ;
        this.subjectId_02 = this.subject.subscribe( RandomCodeGenerator ) ;        
        this.status=1;
        document.getElementById('holder').style.display='block';
    }    
    var end=function(){        
        TimeCodeGenerator.clear();
        RandomCodeGenerator.clear();
        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';
    }    
    return {
        start: start,
        end: end,
        status: 0,
        subject : Location,
        subjectId_01 : null,
        subjectId_02 : null
    };
}());
// end LocationTracker ========
 
var tracker = LocationTracker;
 
</script>
</head>
<body onload="tracker.start()">
<input type="button" value="Start It" onclick="tracker.start();" /> &nbsp;&nbsp;
<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 繼承時, 要很小心, 因為我連續 2 篇在實作時, 都犯了override 父類別方法時,

(1) 無窮迴圈, 因為父類別被子類別給覆蓋了, 表示只會呼自己.

(2) 父類別的方法被子類別不斷覆蓋, 結果只會保留最後一份方法.

(3) 忘了加 this.XXX (冏)

 

ps. 這次sample 無法在jsfiddle 上執行, 所以我就沒有放了.