深入理解JavaScript系列(36):設計模式之中介者模式
- 2020 年 1 月 2 日
- 筆記
中介者模式(Mediator),用一個中介對象來封裝一系列的對象交互。中介者使各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的交互。
正文
軟體開發中,中介者是一個行為設計模式,通過提供一個統一的介面讓系統的不同部分進行通訊。一般,如果系統有很多子模組需要直接溝通,都要創建一個中央控制點讓其各模組通過該中央控制點進行交互。中介者模式可以讓這些子模組不需要直接溝通,而達到進行解耦的目的。
打個比方,平時常見的機場交通控制系統,塔台就是中介者,它控制著飛機(子模組)的起飛和降落,因為所有的溝通都是從飛機向塔台彙報來完成的,而不是飛機之前相互溝通。中央控制系統就是該系統的關鍵,也就是軟體設計中扮演的中介者角色。
我們先用偽程式碼來理解一下:
// 如下程式碼是偽程式碼,請不要過分在意程式碼 // 這裡app命名空間就相當於扮演中介者的角色 var app = app || {}; // 通過app中介者來進行Ajax請求 app.sendRequest = function ( options ) { return $.ajax($.extend({}, options); } // 請求URL以後,展示View app.populateView = function( url, view ){ $.when(app.sendRequest({url: url, method: 'GET'}) .then(function(){ //顯示內容 }); } // 清空內容 app.resetView = function( view ){ view.html(''); }複製程式碼
在JavaScript里,中介者非常常見,相當於觀察者模式上的消息Bus,只不過不像觀察者那樣通過調用pub/sub的形式來實現,而是通過中介者統一來管理,讓我們在觀察者的基礎上來給出一個例子:
var mediator = (function () { // 訂閱一個事件,並且提供一個事件觸發以後的回調函數 var subscribe = function (channel, fn) { if (!mediator.channels[channel]) mediator.channels[channel] = []; mediator.channels[channel].push({ context: this, callback: fn }); return this; }, // 廣播事件 publish = function (channel) { if (!mediator.channels[channel]) return false; var args = Array.prototype.slice.call(arguments, 1); for (var i = 0, l = mediator.channels[channel].length; i < l; i++) { var subscription = mediator.channels[channel][i]; subscription.callback.apply(subscription.context, args); } return this; }; return { channels: {}, publish: publish, subscribe: subscribe, installTo: function (obj) { obj.subscribe = subscribe; obj.publish = publish; } }; } ());複製程式碼
調用程式碼,相對就簡單了:
(function (Mediator) { function initialize() { // 默認值 mediator.name = "dudu"; // 訂閱一個事件nameChange // 回調函數顯示修改前後的資訊 mediator.subscribe('nameChange', function (arg) { console.log(this.name); this.name = arg; console.log(this.name); }); } function updateName() { // 廣播觸發事件,參數為新數據 mediator.publish('nameChange', 'tom'); // dudu, tom } initialize(); // 初始化 updateName(); // 調用 })(mediator);複製程式碼
中介者和觀察者
到這裡,大家可能迷糊了,中介者和觀察者貌似差不多,有什麼不同呢?其實是有點類似,但是我們來看看具體的描述: 觀察者模式,沒有封裝約束的單個對象,相反,觀察者Observer和具體類Subject是一起配合來維護約束的,溝通是通過多個觀察者和多個具體類來交互的:每個具體類通常包含多個觀察者,而有時候具體類里的一個觀察者也是另一個觀察者的具體類。
而中介者模式所做的不是簡單的分發,卻是扮演著維護這些約束的職責。
中介者和外觀模式
很多人可能也比較迷糊中介者和外觀模式的區別,他們都是對現有各模組進行抽象,但有一些微妙的區別。
中介者所做的是在模組之間進行通訊,是多向的,但外觀模式只是為某一個模組或系統定義簡單的介面而不添加額外的功能。系統中的其它模組和外觀模式這個概念沒有直接聯繫,可以認為是單向性。
完整的例子
再給出一個完整的例子:
<!doctype html> <html lang="en"> <head> <title>JavaScript Patterns</title> <meta charset="utf-8"> </head> <body> <div id="results"></div> <script> function Player(name) { this.points = 0; this.name = name; } Player.prototype.play = function () { this.points += 1; mediator.played(); }; var scoreboard = { // 顯示內容的容器 element: document.getElementById('results'), // 更新分數顯示 update: function (score) { var i, msg = ''; for (i in score) { if (score.hasOwnProperty(i)) { msg += '<p><strong>' + i + '</strong>: '; msg += score[i]; msg += '</p>'; } } this.element.innerHTML = msg; } }; var mediator = { // 所有的player players: {}, // 初始化 setup: function () { var players = this.players; players.home = new Player('Home'); players.guest = new Player('Guest'); }, // play以後,更新分數 played: function () { var players = this.players, score = { Home: players.home.points, Guest: players.guest.points }; scoreboard.update(score); }, // 處理用戶按鍵交互 keypress: function (e) { e = e || window.event; // IE if (e.which === 49) { // 數字鍵 "1" mediator.players.home.play(); return; } if (e.which === 48) { // 數字鍵 "0" mediator.players.guest.play(); return; } } }; // go! mediator.setup(); window.onkeypress = mediator.keypress; // 30秒以後結束 setTimeout(function () { window.onkeypress = null; console.log('Game over!'); }, 30000); </script> </body> </html>複製程式碼運行程式碼
總結
中介者模式一般應用於一組對象已定義良好但是以複雜的方式進行通訊的場合,一般情況下,中介者模式很容易在系統中使用,但也容易在系統里誤用,當系統出現了多對多交互複雜的對象群時,先不要急於使用中介者模式,而是要思考一下是不是系統設計有問題。
另外,由於中介者模式把交互複雜性變成了中介者本身的複雜性,所以說中介者對象會比其它任何對象都複雜。