“貓叫、老鼠跑、主人醒”是一個很古老的話題了,大家也都有各自的想法和解決方案。我也是看了很多,一開始的時候是相當的迷糊,這個怎么就是面試題了?考的是啥呀,和編程有關系嗎?又是貓又是老鼠的,暈死了。后來遇到有人寫就去湊湊熱鬧看幾眼。但還是迷迷糊糊。后來學習了面向對象的知識,知道了接口、委托,然后又看了《大話設計模式》。
這時候再回過頭去看貓呀、老鼠什么的,才能看懂一點。為什么只是看懂一點呢?因為還是不知道這個東東和寫代碼有啥關系,呵呵。再再后來,在自己寫代碼的時候,為了解決問題而用了使用了接口;為了提高運行效率而采用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(" [觀察者發出了一個通知 —— " + 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的人可以快速入門用。