Observer模式也叫觀察者模式,是由GoF提出的23種軟體設計模式的一種。 Observer模式是行為模式之一,它的作用是當一個對象的狀態改變時,能夠自動通知其他關聯對象,自動刷新對象狀態。
Observer模式的概念
Observer模式是行為模式之一,它的作用是當一個對象的狀態改變時,能夠自動通知其他關聯對象,自動刷新對象狀態。
Observer模式提供給關聯物件一種同步通訊的手段,使某個物件與依賴它的其他物件之間保持狀態同步。
Observer模式的角色:
Subject(被觀察者)
被觀察的對象。當需要被觀察的狀態改變時,需要通知隊列中所有觀察者物件。 Subject需要維持(添加,刪除,通知)一個觀察者物件的隊列列表。
ConcreteSubject
被觀察者的具體實現。包含一些基本的屬性狀態及其他操作。
Observer(觀察者)
介面或抽象類別。當Subject的狀態改變時,Observer物件將會透過一個callback函式得到通知。
ConcreteObserver
觀察者的具體實現。得到通知後將完成一些具體的業務邏輯處理。
觀察者模式( 又叫發布者-訂閱者模式)應該是最常用的模式之一. 在很多語言裡都得到大量應用. 包括我們平時接觸的dom事件. 也是js和dom之間實現的一種觀察者模式.
div.onclick = function click (){ alert ( ”click' ) }
只要訂閱了div的click事件. 當點擊div的時候, function click就會被觸發.
那麼到底什麼是觀察者模式呢. 先看看生活中的觀察者模式。
好萊塢有句名言. “不要給我打電話, 我會給你打電話”. 這句話就解釋了一個觀察者模式的來龍去脈。 其中「我」是發布者, 「你」是訂閱者。
再舉個例子,我來公司面試的時候,完事之後每個面試官都會對我說:「請留下你的聯絡方式, 有消息我們會通知你」。 這裡「我」是訂閱者, 面試官是發布者。所以我不用每天或每小時都去詢問面試結果, 通訊的主動權掌握在了面試官手上。而我只需要提供一個聯絡方式。
觀察者模式可以很好的實作2個模組之間的解耦。 假如我正在一個團隊裡開發一個html5遊戲. 當遊戲開始的時候,需要載入一些圖片素材。載入好這些圖片之後開始才執行遊戲邏輯. 假設這是一個需要多人合作的專案. 我完成了Gamer和Map模組, 而我的同事A寫了一個圖片載入器loadImage.
loadImage的程式碼如下
loadImage( imgAry, function(){ Map.init(); Gamer.init(); } )
當圖片加載好之後, 再渲染地圖, 執行遊戲邏輯. 嗯, 這個程式運作良好. 突然有一天, 我想起應該給遊戲加上聲音功能. 我應該讓圖片載入器添上一行程式碼.
loadImage( imgAry, function(){ Map.init(); Gamer.init(); Sount.init(); } )
可是寫這個模組的同事A去了外地旅遊. 於是我打電話給他, 餵. 你的loadImage函數在哪, 我能不能改一下, 改了之後有沒有副作用. 如你所想, 各種不淡定的事發生了. 如果當初我們可以這樣寫呢:
loadImage.listen( ”ready', function(){ Map.init(); }) loadImage.listen( ”ready', function(){ Gamer.init(); }) loadImage.listen( ”ready', function(){ Sount.init(); })
loadImage完成之後, 它根本不關心將來會發生什麼, 因為它的工作已經完成了. 接下來它只要發布一個信號.
loadImage.trigger( ”ready' );
那麼監聽了loadImage的'ready'事件的對像都會收到通知. 就像上個面試的例子. 面試官根本不關心面試者們收到面試結果後會去哪吃飯. 他只負責把麵試者的履歷蒐集到一起. 當面試結果出來時照著履歷上的電話挨個通知.
說了這麼多概念, 來一個具體的實現. 實現過程其實很簡單. 面試者把簡歷扔到一個盒子裡, 然後面試官在合適的時機拿著盒子裡的簡歷挨個打電話通知結果.
Events = function() { var listen, log, obj, one, remove, trigger, __this; obj = {}; __this = this; listen = function( key, eventfn ) { //把简历扔盒子, key就是联系方式. var stack, _ref; //stack是盒子 stack = ( _ref = obj[key] ) != null ? _ref : obj[ key ] = []; return stack.push( eventfn ); }; one = function( key, eventfn ) { remove( key ); return listen( key, eventfn ); }; remove = function( key ) { var _ref; return ( _ref = obj[key] ) != null ? _ref.length = 0 : void 0; }; trigger = function() { //面试官打电话通知面试者 var fn, stack, _i, _len, _ref, key; key = Array.prototype.shift.call( arguments ); stack = ( _ref = obj[ key ] ) != null ? _ref : obj[ key ] = []; for ( _i = 0, _len = stack.length; _i < _len; _i++ ) { fn = stack[ _i ]; if ( fn.apply( __this, arguments ) === false) { return false; } } return { listen: listen, one: one, remove: remove, trigger: trigger } }
最後用觀察者模式來做一個成人電視台的小應用.
//订阅者 var adultTv = Event(); adultTv .listen( ”play', function( data ){ alert ( “今天是谁的电影” + data.name ); }); //发布者 adultTv .trigger( ”play', { ‘name': ‘麻生希' } )