JavaScript:設計模式之中介者模式
介紹
中介者模式(Mediator),用一個中介對象來封裝一系列的對象互動。中介者使各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。
本文
軟體開發中,中介者是一個行為設計模式,通過提供一個統一的介面讓系統的不同部分進行通訊。一般,如果系統有很多子模組需要直接溝通,都要建立一個中央控制點讓其各模組通過該中央控制點進行互動。中介者模式可以讓這些子模組不需要直接溝通,而達到進行解耦的目的。
打個比方,平時常見的機場交通控制系統,塔台就是中介者,它控制著飛機(子模組)的起飛和降落,因為所有的溝通都是從飛機向塔台彙報來完成的,而不是飛機之前相互溝通。中央控制系統就是該系統的關鍵,也就是軟體設計中扮演的中介者角色。
我們先用虛擬碼來理解一下:
// 如下代碼是虛擬碼,請不要過分在意代碼// 這裡app命名空間就相當於扮演中介者的角色var app = app || {}; // 通過app中介者來進行Ajax請求app.sendRequest = function ( options ) { return $.ajax($.extend({}, options);} // 請求URL以後,展示Viewapp.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是一起配合來維護約束的,溝通是通過多個觀察者和多個具體類來互動的:每個具體類通常包含多個觀察者,而有時候具體類裡的一個觀察者也是另一個觀察者的具體類。
而中介者模式所做的不是簡單的分發,卻是扮演著維護這些約束的職責。
中介者和面板模式
很多人可能也比較迷糊中介者和面板模式的區別,他們都是對現有各模組進行抽象,但有一些微妙的區別。
中介者所做的是在模組之間進行通訊,是多向的,但面板模式只是為某一個模組或系統定義簡單的介面而不添加額外的功能。系統中的其它模組和面板模式這個概念沒有直接聯絡,可以認為是單向性。
完整的例子
再給出一個完整的例子:
<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 += '
' + i + ': '; msg += score[i]; msg += '
'; } } 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>總結
中介者模式一般應用於一組對象已定義良好但是以複雜的方式進行通訊的場合,一般情況下,中介者模式很容易在系統中使用,但也容易在系統裡誤用,當系統出現了多對多互動複雜的對象群時,先不要急於使用中介者模式,而是要思考一下是不是系統設計有問題。
另外,由於中介者模式把互動複雜性變成了中介者本身的複雜性,所以說中介者對象會比其它任何對象都複雜