摘要:定義觀察者設計模式中有一個對象被稱為根據觀察者維護一個對象列表,自動通知它們對狀態的任何修改。與觀察者模式不同,它允許任何訂閱者實現一個適當的事件處理程序來注冊并接收發布者發布的主題通知。
觀察者設計模式是一個好的設計模式,這個模式我們在開發中比較常見,尤其是它的變形模式訂閱/發布者模式我們更是很熟悉,在我們所熟悉jQuery庫和vue.js框架中我們都有體現。我在面試中也曾經被問到observer和它的變形模式publish/subscribe,說實話,當時有點懵。隨著工作經歷漸多,也認識到它的重要性,特別是當你想要朝著中高級工程師進階時這個東西更是繞不過。
定義觀察者設計模式中有一個對象(被稱為subject)根據觀察者(observer)維護一個對象列表,自動通知它們對狀態的任何修改。
當一個subject要通知觀察者一些有趣的事情時,它會向觀察者發送通知(它可以包含通知主題相關的特定數據)
當我們不在希望某一特定的觀察員被通知它們所登記的主題變化時,這個主題可以將他們從觀察員名單上刪除。
為了從整體上了解設計模式的用法和優勢,回顧已發布的設計模式是非常有用的,這些設計模式的定義與語言無關。在GoF這本書中,觀察者設計模式是這樣定義的:
“一個或多個觀察者對某一subject的狀態感興趣,并通過附加它們自己來注冊它們對該主題的興趣。當觀察者可能感興趣的主題發生變化時,會發送一個通知信息,該通知將調用們個觀察者中的更新方法。當觀察者不再對主題的狀態感興趣時,他們可以簡單地分離自己?!?/p> 組成
擴展我們所學,以組件形式實現observer模式:
主題(subject):維護一個觀察者列表,方便添加或刪除觀察者
觀察者(observer):為需要通知對象更改狀態的對象提供一個更新接口
實際主題(ConcreteSubject):向觀察者發送關于狀態變化的通知,存儲實際觀察者的狀態
實際觀察者(ConcreteObserver):存儲引用到的實際主題,為觀察者實現一個更新接口,以確保狀態與主題的一致。
實現1.對一個subject可能擁有的觀察者列表進行建模:
function ObserverList(){ this.observerList = []; } ObserverList.prototype.add = function( obj ){ return this.observerList.push( obj ); }; ObserverList.prototype.count = function(){ return this.observerList.length; }; ObserverList.prototype.get = function( index ){ if( index > -1 && index < this.observerList.length ){ return this.observerList[ index ]; } }; ObserverList.prototype.indexOf = function( obj, startIndex ){ var i = startIndex; while( i < this.observerList.length ){ if( this.observerList[i] === obj ){ return i; } i++; } return -1; }; ObserverList.prototype.removeAt = function( index ){ this.observerList.splice( index, 1 ); };
2.對subject進行建模,并在觀察者列表中補充添加、刪除、通知觀察者的方法
function Subject(){ this.observers = new ObserverList(); } Subject.prototype.addObserver = function( observer ){ this.observers.add( observer ); }; Subject.prototype.removeObserver = function( observer ){ this.observers.removeAt( this.observers.indexOf( observer, 0 ) ); }; Subject.prototype.notify = function( context ){ var observerCount = this.observers.count(); for(var i=0; i < observerCount; i++){ this.observers.get(i).update( context ); } };
3.為創建一個新的觀察者定義一個框架。框架中的update功能將被稍后的自定義行為覆蓋
// The Observer function Observer(){ this.update = function(){ // ... }; }示例
使用上面定義的觀察者組件,我們做一個demo,定義如下:
在頁面中添加新的可觀察復選框的按鈕;
一個控制復選框將作為一個subject,通知其它的復選框,它們應該被檢查;
正在被添加的復選框容器
然后,我們定義實際的主題和實際的觀察者處理句柄,以便為頁面添加新的觀察者并實現更新接口。
實例代碼如下:
html
js
// 用extend()擴展一個對象 function extend( obj, extension ){ for ( var key in extension ){ obj[key] = extension[key]; } } // DOM 元素的引用 var controlCheckbox = document.getElementById( "mainCheckbox" ), addBtn = document.getElementById( "addNewObserver" ), container = document.getElementById( "observersContainer" ); // 實際主題 (Concrete Subject) // 將控制 checkbox 擴展到 Subject class extend( controlCheckbox, new Subject() ); // 單擊checkbox 通知將發送到它的觀察者 controlCheckbox.onclick = function(){ controlCheckbox.notify( controlCheckbox.checked ); }; addBtn.onclick = addNewObserver; // 實際觀察者(Concrete Observer) function addNewObserver(){ // 新創建的checkbox被添加 var check = document.createElement( "input" ); check.type = "checkbox"; // 擴展 checkbox 用 Observer class extend( check, new Observer() ); // 用自定義的 update 行為覆蓋默認的 check.update = function( value ){ this.checked = value; }; // 添加新的 observer 到 observers 列表中 // 為我們的 main subject controlCheckbox.addObserver( check ); // Append the item to the container container.appendChild( check ); }
在這個示例中我們研究了如何實現和使用觀察者模式,涵蓋了主題(subject), 觀察者(observer),實際/具體對象(ConcreteSubject),實際/具體觀察者(ConcreteObserver)
效果演示:demo
觀察者和發布者訂閱模式之間的差異雖然,觀察者模式很有用,但是在JavaScript中我們經常會用一種被稱為發布/訂閱模式這種變體的觀察者模式。雖然它們很相似,但是這些模式之間還是有區別的。
觀察者模式要求希望接受主題通知的觀察者(或對象)必須訂閱該對象觸發事件的對象(主題)
然而,發布/訂閱模式使用一個主題/事件通道,該通道位于希望接受通知(訂閱者)和觸發事件(發布者)的對象之間。此事件系統允許代碼定義特定用于應用程序的事件,這些事件可以通過自定義參數來傳遞訂閱者所需的值。這樣的思路是為了避免訂閱者和發布者的依賴關系。
與觀察者模式不同,它允許任何訂閱者實現一個適當的事件處理程序來注冊并接收發布者發布的主題通知。
下面一個例子提供了功能實現,使用發布/訂閱模式,可以支持在幕后的publish(),subscribe(),unsubscribe()
// 一個簡單的郵件處理程序 // 接收郵件數 var mailCounter = 0; // 初始化監聽主題的名為 "inbox/newMessage" 的訂閱者. // 呈現一個新消息的預覽 var subscriber1 = subscribe( "inbox/newMessage", function( topic, data ) { // 為了調試目的打印 topic console.log( "A new message was received: ", topic ); // 使用從我們的主題傳遞的數據并向訂閱者顯示消息預覽 $( ".messageSender" ).html( data.sender ); $( ".messagePreview" ).html( data.body ); }); // 這是另一個訂閱者使用相同數據執行不同的任務. // 更新計數器,顯示通過發布者發布所就收的消息數量 var subscriber2 = subscribe( "inbox/newMessage", function( topic, data ) { $(".newMessageCounter").html( ++mailCounter ); }); publish( "inbox/newMessage", [{ sender: "hello@google.com", body: "Hey there! How are you doing today?" }]); // 我們可以在取消訂閱讓我們的訂閱者不能接收到任何新的主題通知如下: // unsubscribe( subscriber1 ); // unsubscribe( subscriber2 );
它的用來促進松散耦合。它們不是直接調用其他對象的方法,而是訂閱另一個對象的特定任務或活動,并在發生改變時得到通知。
優勢觀察者和發布/訂閱模式鼓勵我們認真考慮應用程序的不同部分之間的關系。他們還幫助我們確定那些層次包含了直接關系,而那些層次則可以替換為一系列的主題和觀察者。這可以有效地將應用程序分解為更小的、松散耦合的塊,以改進代碼管理和重用潛力。使用觀察者模式的進一步動機是,我們需要在不適用類緊密耦合的情況下保持相關對象間的一致性。例如,當對象需要能夠通知其他對象是,不需要對這些對象進行假設。
在使用任何模式時,觀察者和主題之間都可以存在動態關系。這題懂了很大的靈活性,當我們的應用程序的不同部分緊密耦合時,實現的靈活性可能不那么容易實現。
雖然它不一定是解決所有問題的最佳方案,但這些模式仍然是設計解耦系統的最佳工具之一,并且應該被認為是任何javascript開發人員的工具鏈中最重要的工具。
劣勢這些模式的一些問題主要源于他們的好處。在發布/訂閱模式中,通過將發布者與訂閱者分離,有時很保證我們的應用程序的某些特定部分可以像我們預期的那樣運行。
例如,發布者可能會假設一個或多個訂閱者正在監聽他們。假設我們使用這樣的假設來記錄或輸出一些應用程序的錯誤。如果執行日志記錄崩潰的訂閱者(或者由于某種原因不能正常運行),那么由于系統的解耦特性,發布者將無法看到這一點。
這種情況的另一種說法是,用戶不知道彼此的存在,對交換發布者的成本視而不見。由于訂閱者和發布者之間的動態關系,更新依賴關系可能很難跟蹤。
發布/訂閱模式的實現發布/訂閱在JavaScript生態系統中很適用,這在很大程度上是因為在核心的ECMAScript實現是事件驅動的。在瀏覽器環境中尤其如此,因為DOM將事件作為腳本的主要交互API。
也就是說,ECMAScript和DOM都不提供在實現代碼中創建自定義事件系統的核心對象或方法(可能只有DOM3 CustomEvent,它是綁定到DOM的,不是通用)。
幸運的是,流行的JavaScript庫,如dojo、jQuery(自定義事件)和YUI已經有了一些實用工具,它們可以幫助輕松實現發布/訂閱系統。下面我們可以看到一些例子:
var pubsub = {}; (function(myObject) { // Storage for topics that can be broadcast // or listened to var topics = {}; // A topic identifier var subUid = -1; // Publish or broadcast events of interest // with a specific topic name and arguments // such as the data to pass along myObject.publish = function( topic, args ) { if ( !topics[topic] ) { return false; } var subscribers = topics[topic], len = subscribers ? subscribers.length : 0; while (len--) { subscribers[len].func( topic, args ); } return this; }; // Subscribe to events of interest // with a specific topic name and a // callback function, to be executed // when the topic/event is observed myObject.subscribe = function( topic, func ) { if (!topics[topic]) { topics[topic] = []; } var token = ( ++subUid ).toString(); topics[topic].push({ token: token, func: func }); return token; }; // Unsubscribe from a specific // topic, based on a tokenized reference // to the subscription myObject.unsubscribe = function( token ) { for ( var m in topics ) { if ( topics[m] ) { for ( var i = 0, j = topics[m].length; i < j; i++ ) { if ( topics[m][i].token === token ) { topics[m].splice( i, 1 ); return token; } } } } return this; }; }( pubsub ));
簡單實現如下:
// Return the current local time to be used in our UI later getCurrentTime = function (){ var date = new Date(), m = date.getMonth() + 1, d = date.getDate(), y = date.getFullYear(), t = date.toLocaleTimeString().toLowerCase(); return (m + "/" + d + "/" + y + " " + t); }; // Add a new row of data to our fictional grid component function addGridRow( data ) { // ui.grid.addRow( data ); console.log( "updated grid component with:" + data ); } // Update our fictional grid to show the time it was last // updated function updateCounter( data ) { // ui.grid.updateLastChanged( getCurrentTime() ); console.log( "data last updated at: " + getCurrentTime() + " with " + data); } // Update the grid using the data passed to our subscribers gridUpdate = function( topic, data ){ if ( data !== undefined ) { addGridRow( data ); updateCounter( data ); } }; // Create a subscription to the newDataAvailable topic var subscriber = pubsub.subscribe( "newDataAvailable", gridUpdate ); // The following represents updates to our data layer. This could be // powered by ajax requests which broadcast that new data is available // to the rest of the application. // Publish changes to the gridUpdated topic representing new entries pubsub.publish( "newDataAvailable", { summary: "Apple made $5 billion", identifier: "APPL", stockPrice: 570.91 }); pubsub.publish( "newDataAvailable", { summary: "Microsoft made $20 million", identifier: "MSFT", stockPrice: 30.85 });
用戶接口通知
接下來我們假設有一個web應用程序負責顯示實時股票信息。
應用程序可能有一個網格用于顯示股票統計數據和顯示最新更新點的計數器。當數據模型發生變化時,應用程序將需要更新網格和計數器。在這個場景中,我們的主題(將發布主題/通知)是數據模型,我們的訂閱者是網格和計數器。
當我們的訂閱者收到通知時,模型本身已經更改,他們可以相應地更新自己。
在我們的實現中,我們的訂閱用戶將收主題“newDataAvailable”,以了解是否有新的股票信息可用。如果一個新的通知發布到這個主題,它將觸發gridUpdate向包含該信息的網格添加一個新的行。它還將更新上一次更新的計數器,以記錄上一次添加的數據
// Return the current local time to be used in our UI later getCurrentTime = function (){ var date = new Date(), m = date.getMonth() + 1, d = date.getDate(), y = date.getFullYear(), t = date.toLocaleTimeString().toLowerCase(); return (m + "/" + d + "/" + y + " " + t); }; // Add a new row of data to our fictional grid component function addGridRow( data ) { // ui.grid.addRow( data ); console.log( "updated grid component with:" + data ); } // Update our fictional grid to show the time it was last // updated function updateCounter( data ) { // ui.grid.updateLastChanged( getCurrentTime() ); console.log( "data last updated at: " + getCurrentTime() + " with " + data); } // Update the grid using the data passed to our subscribers gridUpdate = function( topic, data ){ if ( data !== undefined ) { addGridRow( data ); updateCounter( data ); } }; // Create a subscription to the newDataAvailable topic var subscriber = pubsub.subscribe( "newDataAvailable", gridUpdate ); // The following represents updates to our data layer. This could be // powered by ajax requests which broadcast that new data is available // to the rest of the application. // Publish changes to the gridUpdated topic representing new entries pubsub.publish( "newDataAvailable", { summary: "Apple made $5 billion", identifier: "APPL", stockPrice: 570.91 }); pubsub.publish( "newDataAvailable", { summary: "Microsoft made $20 million", identifier: "MSFT", stockPrice: 30.85 });
其它設計模式相關文章請轉‘大處著眼,小處著手’——設計模式系列
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/90564.html
摘要:關鍵概念理解觀察者設計模式中主要區分兩個概念觀察者指觀察者對象,也就是消息的訂閱者被觀察者指要觀察的目標對象,也就是消息的發布者。 原文首發于微信公眾號:jzman-blog,歡迎關注交流! 最近補一下設計模式相關的知識,關于觀察者設計模式主要從以下幾個方面來學習,具體如下: 什么是觀察者設計模式 關鍵概念理解 通知觀察者的方式 觀察者模式的實現 觀察者模式的優缺點 使用場景 下面...
摘要:為了幫助灰太狼擺脫被老婆平底鍋抽的悲劇,發起了解救灰太狼的行動,必須要知道觀察者模式。持有觀察者對象的集合。設計模式源碼下載 相信大家都有看過《喜洋洋與灰太狼》,說的是灰太狼和羊族的斗爭,而每次的結果都是灰太狼一飛沖天,伴隨著一句我還會回來的......。為灰太狼感到悲哀,抓不到羊,在家也被老婆平底鍋虐待。灰太狼為什么會這么背? 很簡單,灰太狼本身就有暴露行蹤的屬性,羊咩咩就能知曉灰太...
摘要:實際上,設計模式就是通過面向對象的特性,將這些角色解耦觀察者模式本質上就是一種訂閱發布的模型,從邏輯上來說就是一對多的依賴關系。在添加一個觀察者時,把被主題被觀察者對象以構造函數的形式給傳入了觀察者。 每個角色都對應這一個類,比如觀察者模式,觀察者對應著觀察者類,被觀察者對應著被觀察者類。實際上,設計模式就是通過面向對象的特性,將這些角色解耦 觀察者模式本質上就是一種訂閱 / 發布的模...
摘要:總結一下從表面上看觀察者模式里,只有兩個角色觀察者被觀察者而發布訂閱模式,卻不僅僅只有發布者和訂閱者兩個角色,還有第三個角色經紀人存在。參考鏈接觀察者模式發布訂閱模式 做了這么長時間的 菜鳥程序員 ,我好像還沒有寫過一篇關于設計模式的博客...咳咳...意外,純屬意外。所以,我決定,從這一刻起,我要把設計模式在從頭學習一遍,不然都對不起我這 菜鳥 的身份。那這次,就從觀察者模式開始好啦...
摘要:總結一下從表面上看觀察者模式里,只有兩個角色觀察者被觀察者而發布訂閱模式,卻不僅僅只有發布者和訂閱者兩個角色,還有第三個角色經紀人存在。參考鏈接觀察者模式發布訂閱模式 做了這么長時間的 菜鳥程序員 ,我好像還沒有寫過一篇關于設計模式的博客...咳咳...意外,純屬意外。所以,我決定,從這一刻起,我要把設計模式在從頭學習一遍,不然都對不起我這 菜鳥 的身份。那這次,就從觀察者模式開始好啦...
閱讀 3605·2020-12-03 17:42
閱讀 2772·2019-08-30 15:54
閱讀 2231·2019-08-30 15:44
閱讀 577·2019-08-30 14:08
閱讀 976·2019-08-30 14:00
閱讀 1112·2019-08-30 13:46
閱讀 2795·2019-08-29 18:33
閱讀 2920·2019-08-29 14:11