你們都知道,模塊間的耦合不利於代碼的可讀性和可維護性,好的代碼通常都會按功能或者別的將代碼模塊化,那麼模塊化的代碼之間怎麼通訊呢?下面來介紹兩種用於模塊間解耦的設計模式。設計模式
觀察者模式也被稱做消息機制或發佈-訂閱者模式,爲了解決主題對象與觀察者之間功能的耦合。閉包
觀察者模式有一個消息容器,和三個方法,分別是訂閱信息方法、取消訂閱的信息方法、發送訂閱的消息方法。app
例如:ide
/* * 將觀察者放在閉包中,當頁面加載就當即執行 */ const Observer = (function () { // 防止消息隊列暴露而被篡改故將消息容器做爲靜態私有變量保存 let _messages = {}; return { // 註冊信息接口 register() { }, // 發佈信息接口 fire() { }, // 移除信息接口 remove() { } } })();
註冊方法的做用是將訂閱者註冊的消息推入到消息隊列中。模塊化
接受兩個參數:消息類型以及相應的處理動做。函數
代碼以下:測試
// 註冊信息接口 register(type, fn) { // 若是此消息不存在則應該建立一個該消息類型 if(typeof _messages[type] === 'undefined') { // 將動做推入到該消息對應的動做執行隊列中 _messages[type] = [fn]; // 若是此消息存在 } else { // 將動做方法推入該消息對應的動做執行序列中 _messages[type].push(fn); } }
發佈消息方法,其功能是當觀察者發佈一個消息時將全部訂閱者訂閱的消息一次執行。
接收兩個參數:消息類型及動做執行時須要傳遞的參數。this
// 發佈信息接口 fire(type, args) { // 若是該消息沒有被註冊,則返回 if(!_messages[type]) { return; } // 定義消息信息 const events = { type: type, // 消息類型 args: args || {} // 消息攜帶數據 }; // 遍歷消息動做 for(let i = 0, len = _messages[type].length; i < len; i++) { // 依次執行註冊的消息對應的動做序列 _messages[type][i].call(this, events); } }
消息註銷方法:將訂閱者註銷的消息從消息隊列中清除。
接收兩個參數:消息類型及執行某一動做。spa
// 移除信息接口 remove(type, fn) { // 若是消息動做隊列存在 if(_messages[type] instanceof Array) { // 從最後一個消息動做遍歷 for(let i = _messages[type].length - 1; i >= 0; i--) { // 若是存在該動做則在消息動做序列中移除相應動做 _messages[type][i] === fn && _messages[type].splice(i, 1); } } }
總體代碼以下:.net
/* * 將觀察者放在閉包中,當頁面加載就當即執行 */ const Observer = (function () { // 防止消息隊列暴露而被篡改故將消息容器做爲靜態私有變量保存 let _messages = {}; return { // 註冊信息接口 register(type, fn) { // 若是此消息不存在則應該建立一個該消息類型 if(typeof _messages[type] === 'undefined') { // 將動做推入到該消息對應的動做執行隊列中 _messages[type] = [fn]; // 若是此消息存在 } else { // 將動做方法推入該消息對應的動做執行序列中 _messages[type].push(fn); } }, // 發佈信息接口 fire(type, args) { // 若是該消息沒有被註冊,則返回 if(!_messages[type]) { return; } // 定義消息信息 const events = { type: type, // 消息類型 args: args || {} // 消息攜帶數據 }; // 遍歷消息動做 for(let i = 0, len = _messages[type].length; i < len; i++) { // 依次執行註冊的消息對應的動做序列 _messages[type][i].call(this, events); } }, // 移除信息接口 remove(type, fn) { // 若是消息動做隊列存在 if(_messages[type] instanceof Array) { // 從最後一個消息動做遍歷 for(let i = _messages[type].length - 1; i >= 0; i--) { // 若是存在該動做則在消息動做序列中移除相應動做 _messages[type][i] === fn && _messages[type].splice(i, 1); } } } } })();
測試實例以下:
// 測試實例 function register(event) { console.log(event.type, event.args.msg); } Observer.register('test', register); Observer.fire('test', { msg: '傳遞參數' }); Observer.remove('test',register); Observer.fire('test', { msg: '傳遞參數' });
當用戶發佈評論時,會在評論展現模塊末尾處追加新的評論,與此同時用戶的消息模塊的消息數量也會遞增。若是用戶刪除留言區的信息時,用戶的消息模塊消息數量也會遞減。
其中,評論展現模塊,消息模塊,評論模塊是三個獨立的模塊。
實現以下:
評論展現模塊:
(function(){ /** * 追加一則消息 * @param e */ function addMsgItem(e) { let text = e.args.text, // 獲取消息中用戶添加的文本內容 ul = $('msg'), // 留言容器元素 li = document.createElement('li'), // 建立內容容器元素 span = document.createElement('span'); // 刪除按鈕 li.innerHTML = text; // 寫入評論 // 關閉按鈕 span.onclick = function() { ul.removeChild(li); // 發佈刪除留言消息 Observer.fire('removeCommentMessage',{ num: -1 }); }; // 添加刪除按鈕 ul.appendChild(li); // 添加留言節點 ul.appendChild(li); } // 註冊添加評論信息 Observer.register('addCommentMessage', addMsgItem); })();
消息模塊:
(function() { /** * 更改用戶消息數目 * @param e */ function changeMseNum(e) { // 獲取須要增長的用戶消息數目 let num = e.args.num; const $msgNum = $('msgNum'); // 增長用戶消息數目並寫入頁面中 $msgNum.innerHTML = parseInt($msgNum.innerHTML) + num; } // 註冊添加評論信息 Observer.register('addCommentMessage',changeMseNum); Observer.register('removeCommentMessage', changeMseNum); })();
評論模塊:
(function(){ // 用戶點擊提交按鈕 $('useSubmit').onclick = function() { // 獲取用戶輸入框中輸入的信息 const text = $('useInput'); // 若是消息爲空則提交失敗 if(!text.value) { return; } // 發佈一則評論信息 Observer.fire('addCommentMessage',{ text: text.value, // 消息評論內容 num: 1 // 消息評論數目 }); text.value = ''; } })();
demo地址:http://jsrun.net/PDYKp/show
觀察者模式也適用於對象間的解耦,代碼以下:
// 對象間的解耦 class Student { constructor(result) { const self = this; self.result = result; self.say = function() { console.log(self.result); } } answer(question) { // 註冊參數問題 Observer.register(question,this.say); } sleep(question) { console.log(`${this.result} ${question}已被註銷`); // 取消對老師問題對監聽 Observer.remove(question, this.say); } } class Teacher { constructor() { } ask(question) { console.log(`問題是 ${question}`); // 發佈提問消息 Observer.fire(question); } } // 實例 const student1 = new Student('學生1回答問題'); const student2 = new Student('學生2回答問題'); const student3 = new Student('學生3回答問題'); const teacher = new Teacher(); // 學生監聽老師提問問題 student1.answer('什麼是設計模式?'); student1.answer('簡述觀察者模式'); student2.answer('什麼是設計模式?'); student3.answer('什麼是設計模式?'); student3.answer('簡述觀察者模式'); // 老師提問問題 teacher.ask('什麼是設計模式?'); // 學生3在'簡述觀察者模式的時候睡着了' student3.sleep('簡述觀察者模式'); teacher.ask('簡述觀察者模式');
運行結果以下:
中介者模式經過模塊間或者對象間的複雜通訊,來解決模塊或對象間的耦合。
本質是分裝多個對象的交互,而且這些對象的交互通常都是在中介者內部實現的。
代碼實現以下:
/** * 中介者對象 */ const Mediator = (function() { // 消息對象 let _msg = {}; return { /** * 訂閱消息方法 * @param type 消息名稱 * @param action 消息回調函數 */ register(type, action) { // 若是消息存在 if(_msg[type]) { // 存入回調函數 _msg[type].push(action); } else { // 不存在,則創建該消息容器 _msg[type] = [action]; } }, /** * 發佈消息方法 * @param type 消息名稱 */ send(type) { // 若是該消息已經被訂閱 if(_msg[type]) { // 遍歷已存儲的消息回調函數 for(let i = 0, len = _msg[type].length; i < len; i++) { // 執行該回調函數 _msg[type][i] && _msg[type][i](); } } } } })();
測試代碼以下:
// 訂閱demo消息,執行回調函數--輸出first Mediator.register('demo',()=> { console.log('first'); }); // 訂閱demo消息,執行回調函數--輸出second Mediator.register('demo', ()=> { console.log('second'); }); // 發佈demo消息 Mediator.send('demo');
添加一個用戶首頁導航設置,導航上面有消息提醒和導航網址。具體以下:
用戶收藏導航模塊有消息提醒和導航網址功能;
推薦用戶導航有推薦用戶導航內的導航有消息提醒功能;
最近經常使用導航有最近經常使用導航欄內有導航網址功能;
代碼實現:
用戶收藏導航模塊
(function(){ // 訂閱隱藏用戶收藏導航消息提醒消息 Mediator.register('hideAllNavNum', () => { showHideNavWidget('collectionNav', 'b', false); }); // 訂閱顯示用戶收藏導航消息提醒消息 Mediator.register('showAllNavNum', () => { showHideNavWidget('collectionNav', 'b', true); }); // 訂閱隱藏用戶收藏導航消息提醒消息 Mediator.register('hideAllNavUrl', () => { showHideNavWidget('collectionNav', 'span', false); }); // 訂閱顯示用戶收藏導航消息提醒消息 Mediator.register('showAllNavUrl', () => { showHideNavWidget('collectionNav', 'span', true); }); })();
推薦用戶導航
(function() { // 訂閱隱藏推薦用戶導航消息提醒消息 Mediator.register('hideAllNavNum', () => { showHideNavWidget('recommendNav', 'b', false); }); // 訂閱顯示推薦用戶導航消息提醒消息 Mediator.register('showAllNavNum', () => { showHideNavWidget('recommendNav', 'b', true); }); })();
最近經常使用導航
(function() { // 訂閱隱藏最近經常使用導航消息 Mediator.register('hideAllNavUrl', () => { showHideNavWidget('recentlyNav', 'span', 'hide'); }); // 訂閱顯示最近經常使用導航消息 Mediator.register('showAllNavUrl', () => { showHideNavWidget('recentlyNav', 'span', 'show'); }); })();
設置層模塊
(function () { // 消息提醒選框 const hideNum = document.getElementById('hideNum'); // 網址選框 const hideUrl = document.getElementById('hideUrl'); // 消息提醒選框事件 hideNum.addEventListener('change',function(){ if(this.checked){ // 中介者發佈隱藏消息提醒功能消息 Mediator.send('hideAllNavNum'); } else { // 中介者發佈隱藏消息提醒功能消息 Mediator.send('showAllNavNum'); } },false); // 網址選框事件 hideUrl.addEventListener('change',function() { if(hideUrl.checked) { // 中介者發佈隱藏全部網址功能消息 Mediator.send('hideAllNavUrl'); } else { // 中介者發佈顯示全部網址功能消息 Mediator.send('showAllNavUrl'); } }, false); })();
demo地址:http://jsrun.net/QDYKp/show
觀察者模式最主要的做用是解決類和對象之間的耦合,解耦兩個相互依賴的對象,使其依賴於觀察者的消息機制。對於任意一個訂閱者對象來講,其餘訂閱者對象的改變不會影響到自身。對於每個訂閱者來講,其自身既能夠是消息的發出者也能夠是消息的執行者。
中介者模式中的消息的發送方只有一個,就是中介者對象,中介者對象不能訂閱消息,只有那些活躍對象(訂閱者)才能夠訂閱中介者的消息。也能夠將中介者模式看做將消息系統封裝在中介者對象內部,中介者對象只能是消息的發送者。
中介者模式與觀察者模式比較: 都是經過消息傳遞實現對象間或模塊間的解耦。 觀察者中的訂閱者是雙向的,既能夠是消息的發佈者,也能夠是消息的訂閱者。 中介者模式中,訂閱者是單項的,只能是消息的訂閱,消息同一有中介者對象發佈,全部訂閱者對象間接地被中介者管理。