以【貓叫、老鼠跑、主人醒】為例子,使用 javascript 來實現 觀察者模式 (有在線演示)


 

  “貓叫、老鼠跑、主人醒”是一個很古老的話題了,大家也都有各自的想法和解決方案。我也是看了很多,一開始的時候是相當的迷糊,這個怎么就是面試題了?考的是啥呀,和編程有關系嗎?又是貓又是老鼠的,暈死了。后來遇到有人寫就去湊湊熱鬧看幾眼。但還是迷迷糊糊。后來學習了面向對象的知識,知道了接口、委托,然后又看了《大話設計模式》。

  這時候再回過頭去看貓呀、老鼠什么的,才能看懂一點。為什么只是看懂一點呢?因為還是不知道這個東東和寫代碼有啥關系,呵呵。再再后來,在自己寫代碼的時候,為了解決問題而用了使用了接口;為了提高運行效率而采用js+ajax時,要解決“主頁面”和子頁面(iframe里的頁面)的事件調用的問題。都解決了之后,才對觀察者模式有了更深入的理解。再去看相關的文章才能看得懂。(好像我把順序給弄反了呀)

 

  webform的時候,感覺觀察者模式比較雞肋,因為web是無狀態的,客戶端可以主動訪問服務器端,但是服務器端卻不能主動找到客戶端。這個……太煩人了。webform是怎么解決的呢?每次訪問的時候都重新注冊一遍。這個效率呀,雖然好像影響不大,但是知道原理之后就是感覺不爽。

 

  學習js快一年了,越學習越是感覺js的強大,同時也感覺,如果用javascript來實現“貓叫、老鼠跑、主人醒”的話,是不是更易讀一些呢?看C#代碼的時候,輕則接口、重則委托,如果這些我都不熟悉那么我咋看?當然你可以怪我基礎知識不扎實,呵呵。但是我想js能夠更明確的表達出來觀察者模式的意圖吧。

 

  我的理解和大家的好像不大一樣,貓叫,聲音傳遞了出去,老鼠聽到了開始跑,跑動的聲音發出去了,主人被吵醒。那么誰是觀察者呢?傳遞聲音的介質 —— 空氣!空氣在觀察哪里發出了聲音,然后把聲音傳遞出去,傳遞給訂閱者。這個是很自然而然的事情吧,沒有任何的牽強。

  如果說老鼠是觀察者,他在主動觀察貓是不是發出了聲音,聽到了就跑,這個還勉強說得過去。但是主人呢?按照這個邏輯來說,主人也是觀察者,他在主動觀察老鼠跑動的聲音,聽到了就會醒。這個就說不過去了。人在睡覺的時候還會去主動觀察聲音嗎?士兵在執行任務的時候會這么做,但是睡不好覺的。為了睡個好覺是不會去主動觀察的。那么為什么會被吵醒呢?不是說好了,是“吵”醒嘛,是被動接收的,就是說他是訂閱者,訂閱了消息。有消息告訴我,而不是主動觀察消息。同理老鼠也是一樣。

 

  好了言歸正傳,開始說代碼實現

  因為是js的,所以需要先介紹一下頁面結構,因為老鼠、貓、主人都是獨立個體,可以不放在一個頁面里,所以我就設置了一個頁面,里面放了三個iframe,分別指向 老鼠頁、貓頁、主人頁。另一個原因就是,我們在做后台管理的時候,一般也是先弄一個頁作為主頁,然后在里面放個樹,在放個tab標簽,然后動態開n個iframe,每個iframe都是一個簡單的功能頁面。這樣就和實際情況比較接近了。

 

  先定義傳播聲音的介質 —— 空氣

//定義傳播聲音的介質 —— 空氣
        var air = function () {
            var events = {
                SubjectEvent: []        //訂閱者的注冊事件,當有情況時觸發這些事件

            };

            //添加訂閱者的接收消息的事件。
            this.addSubjectEvent = function (e) {
                events.SubjectEvent.push(e);
                writeLog("[觀察者接收了一個注冊事件,接收事件數量:" + events.SubjectEvent.length + "。]<br/>");
            };

            //監聽聲音。理論上是去監聽,但是這里還是得被動調用。
            this.sendSound = function (info) {
                writeLog("[觀察者開始傳遞聲音,接收事件數量:" + events.SubjectEvent.length + "。]<br/>");
                Notify(info);
            }

            //發出通知。
            var Notify = function (info) {
                //有發出聲音的時候通知訂閱者,就是遍歷他們注冊的事件
                for (var i = 0; i < events.SubjectEvent.length; i++) {
                    var sound = "";
                    if (typeof info.Sound != "undefined") {
                        sound = info.Sound;
                    }

                    writeLog("&nbsp;&nbsp;[觀察者發出了一個通知 —— " + sound + "]<br/>");
                    events.SubjectEvent[i](info);
                }
            }
        }

 

然后分別是貓、老鼠、和主人

var cat = function (name) {
             var name = name;
             this.sendSound;

             //貓叫
             this.cry = function () {
                 //發出聲音
                 parent.writeLog("<br/>" + name + "開始喵喵叫。<br/>");
                 if (typeof this.sendSound != "undefined") {
                     //觸發事件
                     parent.writeLog("聲音傳遞了出去。<br/>");
                     this.sendSound({ Sound: "喵喵叫", Volume: 3 });

                 }
             }

             parent.writeLog("我是" + name + ",有人按俺就叫。<br/>");
         }

 

老鼠

 //可以看做是定義了一個類,當然並不准確
         var mouse = function (name) {
             var name = name;   //可以看做是私有成員
             this.sendSound;    //可以看做是公有成員

             var _self = this;

             //事件——老鼠跑
             this.run = function (info) {
                 parent.writeLog(name + "聽到了聲音。<br/>");
                 if (typeof info.Sound != "undefined") {
                     //有聲音,判斷
                     if (info.Sound == "逛逛跑") {
                         //自己跑步的聲音,不處理了,要不就死循環。
                         parent.writeLog(name + "聽到了自己的跑步聲音。<br/>");
                     }
                     else {
                         //其他聲音,跑吧,不判斷了。
                         parent.writeLog(name + "開始狂飆。<br/>");

                         //發出聲音
                         if (typeof _self.sendSound != "undefined") {
                             parent.writeLog(name + "狂飆發出了聲音。<br/>");
                             _self.sendSound({ Sound: "逛逛跑", Volume: 7 });

                         }
                     }
                    
                 };
             }

             parent.writeLog("我是" + name + ",出來找吃的。<br/>");

         }

 

主人

 var person = function (name) {
             var name = name;
             var isWark = false;
             this.sendSound;

             //主人聽到聲音
             this.hearSound = function (info) {

                 if (isWark) {
                     parent.writeLog(name + "已經醒了。<br/>");
                 }
                 else {
                     if (typeof info.Volume != "undefined") {
                         if (info.Volume <= 5) {
                             parent.writeLog("聲音小," + name + "繼續睡覺。<br/>");
                         }
                         else if (info.Volume > 5) {
                             parent.writeLog("聲音大," + name + "被吵醒。<br/>");
                             isWark = true;
                         }
                     }
                     
                 }
             };

             parent.writeLog("我是" + name + ",俺睡着了。<br/>");
         };

 

然后是實例化和調用

 

var myAir = new air();

        //顯示消息
        function writeLog(msg) {
            
            document.getElementById("msg").innerHTML += msg;
        };

 

 var Tom = new cat("小貓咪湯姆");

         window.onload = function () {
             Tom.sendSound = parent.myAir.sendSound;  //發出聲音的事件
             
         };

 

  var Jerry = new mouse("小老鼠傑瑞");
         var longtao = new mouse("可憐的龍套甲");

         window.onload = function () {
             Jerry.sendSound = parent.myAir.sendSound;  //發出聲音的事件
             longtao.sendSound = parent.myAir.sendSound;  //發出聲音的事件

             //傑瑞很聰明,注意聽聲音,申請了一個訂閱 —— 有聲音俺就 run
             parent.myAir.addSubjectEvent(Jerry.run);

             //為了做對比,龍套甲就不能去申請了。

         };

 

 var Master = new person("主人");

         window.onload = function () {
             //主人睡覺了,但是為了發生意外,還是申請一個訂閱吧,要不然萬一着火了我還呼呼呢。
             parent.myAir.addSubjectEvent(Master.hearSound);
             
         };

 

javascript里沒有接口和委托的概念,但是並不是說沒有這些功能,而是說不用去定義,直接用就可以。

比如 myAir.addSubjectEvent(),可以直接把一個“事件”(Master.hearSound)當作參數傳遞進去,非常方便。

 

最后是開始表演

 

<span onclick="Tom.cry()">點俺俺就喵喵叫</span>

 

 

 在線演示 (需要點一下第二個iframe里的“點俺俺就喵喵叫”,才會開始運行)

 

 

 

ps:快速理解javascript 的一種方法。這個方法不准確,只是用於熟悉c#、但是不熟悉js的人可以快速入門用。

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM