摘要:發布者的狀態發生變化時就會通知所有的訂閱者,使得它們能夠自動更新自己。觀察者模式的中心思想就是促進松散耦合,一為時間上的解耦,二為對象之間的解耦。參考設計模式與開發實踐第章發布訂閱模式設計模式第章第節觀察者模式
概述
觀察者模式又叫發布 - 訂閱模式(Publish/Subscribe),它定義了一種一對多的關系,讓多個觀察者對象同時監聽某一個目標對象(為了方便理解,以下將觀察者對象叫做訂閱者,將目標對象叫做發布者)。發布者的狀態發生變化時就會通知所有的訂閱者,使得它們能夠自動更新自己。
觀察者模式的使用場合就是:當一個對象的改變需要同時改變其它對象,并且它不知道具體有多少對象需要改變的時候,就應該考慮使用觀察者模式。
觀察者模式的中心思想就是促進松散耦合,一為時間上的解耦,二為對象之間的解耦。讓耦合的雙方都依賴于抽象,而不是依賴于具體,從而使得各自的變化都不會影響到另一邊的變化。
實現(function (window, undefined) { var _subscribe = null, _publish = null, _unsubscribe = null, _shift = Array.prototype.shift, // 刪除數組的第一個 元素,并返回這個元素 _unshift = Array.prototype.unshift, // 在數組的開頭添加一個或者多個元素,并返回數組新的length值 namespaceCache = {}, _create = null, each = function (ary, fn) { var ret = null; for (var i = 0, len = ary.length; i < len; i++) { var n = ary[i]; ret = fn.call(n, i, n); } return ret; }; // 訂閱消息 _subscribe = function (key, fn, cache) { if (!cache[key]) { cache[key] = []; } cache[key].push(fn); }; // 取消訂閱(取消全部或者指定消息) _unsubscribe = function (key, cache, fn) { if (cache[key]) { if (fn) { for (var i = cache[key].length; i >= 0; i--) { if (cache[key][i] === fn) { cache[key].splice(i, 1); } } } else { cache[key] = []; } } }; // 發布消息 _publish = function () { var cache = _shift.call(arguments), key = _shift.call(arguments), args = arguments, _self = this, ret = null, stack = cache[key]; if (!stack || !stack.length) { return; } return each(stack, function () { return this.apply(_self, args); }); }; // 創建命名空間 _create = function (namespace) { var namespace = namespace || "default"; var cache = {}, offlineStack = {}, // 離線事件,用于先發布后訂閱,只執行一次 ret = { subscribe: function (key, fn, last) { _subscribe(key, fn, cache); if (!offlineStack[key]) { offlineStack[key] = null; return; } if (last === "last") { // 指定執行離線隊列的最后一個函數,執行完成之后刪除 offlineStack[key].length && offlineStack[key].pop()(); // [].pop => 刪除一個數組中的最后的一個元素,并且返回這個元素 } else { each(offlineStack[key], function () { this(); }); } offlineStack[key] = null; }, one: function (key, fn, last) { _unsubscribe(key, cache); this.subscribe(key, fn, last); }, unsubscribe: function (key, fn) { _unsubscribe(key, cache, fn); }, publish: function () { var fn = null, args = null, key = _shift.call(arguments), _self = this; _unshift.call(arguments, cache, key); args = arguments; fn = function () { return _publish.apply(_self, args); }; if (offlineStack && offlineStack[key] === undefined) { offlineStack[key] = []; return offlineStack[key].push(fn); } return fn(); } }; return namespace ? (namespaceCache[namespace] ? namespaceCache[namespace] : namespaceCache[namespace] = ret) : ret; }; window.pubsub = { create: _create, // 創建命名空間 one: function (key, fn, last) { // 訂閱消息,只能單一對象訂閱 var pubsub = this.create(); pubsub.one(key, fn, last); }, subscribe: function (key, fn, last) { // 訂閱消息,可多對象同時訂閱 var pubsub = this.create(); pubsub.subscribe(key, fn, last); }, unsubscribe: function (key, fn) { // 取消訂閱,(取消全部或指定消息) var pubsub = this.create(); pubsub.unsubscribe(key, fn); }, publish: function () { // 發布消息 var pubsub = this.create(); pubsub.publish.apply(this, arguments); } }; })(window, undefined);應用
假如我們正在開發一個商城網站,網站里有header頭部、nav導航、消息列表、購物車等模塊。這幾個模塊的渲染有一個共同的前提條件,就是必須先用ajax異步請求獲取用戶的登錄信息。
至于ajax請求什么時候能成功返回用戶信息,這點我們沒有辦法確定。更重要的一點是,我們不知道除了header頭部、nav導航、消息列表、購物車之外,將來還有哪些模塊需要使用這些用戶信息。如果它們和用戶信息模塊產生了強耦合,比如下面這樣的形式:
login.succ(function (data) { header.setAvatar(data.avatar); // 設置header模塊的頭像 nav.setAvatar(data.avatar); // 設置導航模塊的頭像 message.refresh(); // 刷新消息列表 cart.refresh(); // 刷新購物車列表 });
現在登錄模塊是由你負責編寫的,但我們還必須了解header模塊里設置頭像的方法叫setAvatar、購物車模塊里刷新的方法叫refresh,這種耦合性會使程序變得僵硬,header模塊不能隨意再改變setAvatar的方法名。這是針對具體實現編程的典型例子,針對具體實現編程是不被贊同的。
等到有一天,項目中又新增了一個收貨地址管理的模塊,這個模塊是由另一個同事所寫的,此時他就必須找到你,讓你登錄之后刷新一下收貨地址列表。于是你又翻開你3個月前寫的登錄模塊,在最后部分加上這行代碼:
login.succ(function (data) { header.setAvatar(data.avatar); nav.setAvatar(data.avatar); message.refresh(); cart.refresh(); address.refresh(); // 增加這行代碼 });
我們就會越來越疲于應付這些突如其來的業務要求,不停地重構這些代碼。
用觀察者模式重寫之后,對用戶信息感興趣的業務模塊將自行訂閱登錄成功的消息事件。當登錄成功時,登錄模塊只需要發布登錄成功的消息,而業務方接受到消息之后,就會開始進行各自的業務處理,登錄模塊并不關心業務方究竟要做什么,也不想去了解它們的內部細節。改善后的代碼如下:
$.ajax("http:// xxx.com?login", function(data) { // 登錄成功 pubsub.publish("loginSucc", data); // 發布登錄成功的消息 }); // 各模塊監聽登錄成功的消息: var header = (function () { // header模塊 pubsub.subscribe("loginSucc", function(data) { header.setAvatar(data.avatar); }); return { setAvatar: function(data){ console.log("設置header模塊的頭像"); } }; })(); var nav = (function () { // nav模塊 pubsub.subscribe("loginSucc", function(data) { nav.setAvatar(data.avatar); }); return { setAvatar: function(avatar) { console.log("設置nav模塊的頭像"); } }; })();
如上所述,我們隨時可以把setAvatar的方法名改成setTouxiang。如果有一天在登錄完成之后,又增加一個刷新收貨地址列表的行為,那么只要在收貨地址模塊里加上監聽消息的方法即可,而這可以讓開發該模塊的同事自己完成,你作為登錄模塊的開發者,永遠不用再關心這些行為了。代碼如下:
var address = (function () { // 地址模塊 pubsub.subscribe("loginSucc", function(obj) { address.refresh(obj); }); return { refresh: function(avatar) { console.log("刷新收貨地址列表"); } }; })();優缺點 優點
支持簡單的廣播通信,自動通知所有已經訂閱過的對象;
頁面載入后發布者很容易與訂閱者存在一種動態關聯,增加了靈活性;
發布者與訂閱者之間的抽象耦合關系能夠多帶帶擴展以及重用。
缺點創建訂閱者本身要消耗一定的時間和內存,而且當你訂閱一個消息后,也許此消息最后都未發生,但這個訂閱者會始終存在于內存中;
雖然可以弱化對象之間的聯系,但如果過度使用的話,對象和對象之間的必要聯系也將被深埋在背后,會導致程序難以跟蹤維護和理解。
參考《JavaScript設計模式與開發實踐》 第 8 章 發布—訂閱模式
《JavaScript設計模式》 第 9 章 第 5 節 Observer(觀察者)模式
http://www.cnblogs.com/TomXu/archive/2012/03/02/2355128.html
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/89649.html
摘要:設計模式是以面向對象編程為基礎的,的面向對象編程和傳統的的面向對象編程有些差別,這讓我一開始接觸的時候感到十分痛苦,但是這只能靠自己慢慢積累慢慢思考。想繼續了解設計模式必須要先搞懂面向對象編程,否則只會讓你自己更痛苦。 JavaScript 中的構造函數 學習總結。知識只有分享才有存在的意義。 是時候替換你的 for 循環大法了~ 《小分享》JavaScript中數組的那些迭代方法~ ...
摘要:是文檔的一種表示結構。這些任務大部分都是基于它。這個實踐的重點是把你在前端練級攻略第部分中學到的一些東西和結合起來。一旦你進入框架部分,你將更好地理解并使用它們。到目前為止,你一直在使用進行操作。它是在前端系統像今天這樣復雜之前編寫的。 本文是 前端練級攻略 第二部分,第一部分請看下面: 前端練級攻略(第一部分) 在第二部分,我們將重點學習 JavaScript 作為一種獨立的語言,如...
摘要:開發中,我們或多或少地接觸了設計模式,但是很多時候不知道自己使用了哪種設計模式或者說該使用何種設計模式。本文意在梳理常見設計模式的特點,從而對它們有比較清晰的認知。 showImg(https://segmentfault.com/img/remote/1460000014919705?w=640&h=280); 開發中,我們或多或少地接觸了設計模式,但是很多時候不知道自己使用了哪種設...
摘要:大潮來襲前端開發能做些什么去年谷歌和火狐針對提出了的標準,顧名思義,即的體驗方式,我們可以戴著頭顯享受沉浸式的網頁,新的標準讓我們可以使用語言來開發。 VR 大潮來襲 --- 前端開發能做些什么 去年谷歌和火狐針對 WebVR 提出了 WebVR API 的標準,顧名思義,WebVR 即 web + VR 的體驗方式,我們可以戴著頭顯享受沉浸式的網頁,新的 API 標準讓我們可以使用 ...
閱讀 2796·2021-11-24 09:39
閱讀 2556·2021-11-23 09:51
閱讀 1858·2021-11-17 09:33
閱讀 1749·2021-10-22 09:54
閱讀 1879·2021-08-16 11:00
閱讀 3432·2019-08-30 15:53
閱讀 1740·2019-08-30 13:19
閱讀 2912·2019-08-30 12:49