橋接模式(Bridge)將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。
橋接模式最常用在事件監(jiān)控上,先看一段代碼:
addEvent(element, 'click', getBeerById); function getBeerById(e) { var id = this.id; asyncRequest('GET', 'beer.uri?id=' + id, function(resp) { // Callback response. console.log('Requested Beer: ' + resp.responseText); }); }
上述代碼,有個(gè)問(wèn)題就是getBeerById必須要有瀏覽器的上下文才能使用,因?yàn)槠鋬?nèi)部使用了this.id這個(gè)屬性,如果沒(méi)用上下文,那就歇菜了。所以說(shuō)一般稍微有經(jīng)驗(yàn)的程序員都會(huì)將程序改造成如下形式:
function getBeerById(id, callback) { // 通過(guò)ID發(fā)送請(qǐng)求,然后返回?cái)?shù)據(jù) asyncRequest('GET', 'beer.uri?id=' + id, function(resp) { // callback response callback(resp.responseText); }); }
實(shí)用多了,對(duì)吧?首先ID可以隨意傳入,而且還提供了一個(gè)callback函數(shù)用于自定義處理函數(shù)。但是這個(gè)和橋接有什么關(guān)系呢?這就是下段代碼所要體現(xiàn)的了:
addEvent(element, 'click', getBeerByIdBridge); function getBeerByIdBridge (e) { getBeerById(this.id, function(beer) { console.log('Requested Beer: '+beer); }); }
這里的getBeerByIdBridge就是我們定義的橋,用于將抽象的click事件和getBeerById連接起來(lái),同時(shí)將事件源的ID,以及自定義的call函數(shù)(console.log輸出)作為參數(shù)傳入到getBeerById函數(shù)里。
這個(gè)例子看起來(lái)有些簡(jiǎn)單,我們?cè)賮?lái)一個(gè)復(fù)雜點(diǎn)的實(shí)戰(zhàn)例子。
我們要構(gòu)建一個(gè)隊(duì)列,隊(duì)列里存放了很多ajax請(qǐng)求,使用隊(duì)列(queue)主要是因?yàn)橐_保先加入的請(qǐng)求先被處理。任何時(shí)候,我們可以暫停請(qǐng)求、刪除請(qǐng)求、重試請(qǐng)求以及支持對(duì)各個(gè)請(qǐng)求的訂閱事件。
在正式開(kāi)始之前,我們先定義一下核心的幾個(gè)封裝函數(shù),首先第一個(gè)是異步請(qǐng)求的函數(shù)封裝:
var asyncRequest = (function () { function handleReadyState(o, callback) { var poll = window.setInterval( function () { if (o && o.readyState == 4) { window.clearInterval(poll); if (callback) { callback(o); } } }, 50 ); } var getXHR = function () { var http; try { http = new XMLHttpRequest; getXHR = function () { return new XMLHttpRequest; }; } catch (e) { var msxml = [ 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP' ]; for (var i = 0, len = msxml.length; i < len; ++i) { try { http = new ActiveXObject(msxml[i]); getXHR = function () { return new ActiveXObject(msxml[i]); }; break; } catch (e) { } } } return http; }; return function (method, uri, callback, postData) { var http = getXHR(); http.open(method, uri, true); handleReadyState(http, callback); http.send(postData || null); return http; }; })();
上述封裝的自執(zhí)行函數(shù)是一個(gè)通用的Ajax請(qǐng)求函數(shù),相信屬性Ajax的人都能看懂了。
接下來(lái)我們定義一個(gè)通用的添加方法(函數(shù))的方法:
Function.prototype.method = function (name, fn) { this.prototype[name] = fn; return this; };
最后再添加關(guān)于數(shù)組的2個(gè)方法,一個(gè)用于遍歷,一個(gè)用于篩選:
if (!Array.prototype.forEach) { Array.method('forEach', function (fn, thisObj) { var scope = thisObj || window; for (var i = 0, len = this.length; i < len; ++i) { fn.call(scope, this[i], i, this); } }); } if (!Array.prototype.filter) { Array.method('filter', function (fn, thisObj) { var scope = thisObj || window; var a = []; for (var i = 0, len = this.length; i < len; ++i) { if (!fn.call(scope, this[i], i, this)) { continue; } a.push(this[i]); } return a; }); }
因?yàn)橛械男滦蜑g覽器已經(jīng)支持了這兩種功能(或者有些類庫(kù)已經(jīng)支持了),所以要先判斷,如果已經(jīng)支持的話,就不再處理了。
觀察者在隊(duì)列里的事件過(guò)程中扮演著重要的角色,可以隊(duì)列處理時(shí)(成功、失敗、掛起)訂閱事件:
window.DED = window.DED || {}; DED.util = DED.util || {}; DED.util.Observer = function () { this.fns = []; } DED.util.Observer.prototype = { subscribe: function (fn) { this.fns.push(fn); }, unsubscribe: function (fn) { this.fns = this.fns.filter( function (el) { if (el !== fn) { return el; } } ); }, fire: function (o) { this.fns.forEach( function (el) { el(o); } ); } };
首先訂閱了隊(duì)列的主要屬性和事件委托:
DED.Queue = function () { // 包含請(qǐng)求的隊(duì)列. this.queue = []; // 使用Observable對(duì)象在3個(gè)不同的狀態(tài)上,以便可以隨時(shí)訂閱事件 this.onComplete = new DED.util.Observer; this.onFailure = new DED.util.Observer; this.onFlush = new DED.util.Observer; // 核心屬性,可以在外部調(diào)用的時(shí)候進(jìn)行設(shè)置 this.retryCount = 3; this.currentRetry = 0; this.paused = false; this.timeout = 5000; this.conn = {}; this.timer = {}; };
然后通過(guò)DED.Queue.method的鏈?zhǔn)秸{(diào)用,則隊(duì)列上添加了很多可用的方法:
DED.Queue. method('flush', function () { // flush方法 if (!this.queue.length > 0) { return; } if (this.paused) { this.paused = false; return; } var that = this; this.currentRetry++; var abort = function () { that.conn.abort(); if (that.currentRetry == that.retryCount) { that.onFailure.fire(); that.currentRetry = 0; } else { that.flush(); } }; this.timer = window.setTimeout(abort, this.timeout); var callback = function (o) { window.clearTimeout(that.timer); that.currentRetry = 0; that.queue.shift(); that.onFlush.fire(o.responseText); if (that.queue.length == 0) { that.onComplete.fire(); return; } // recursive call to flush that.flush(); }; this.conn = asyncRequest( this.queue[0]['method'], this.queue[0]['uri'], callback, this.queue[0]['params'] ); }). method('setRetryCount', function (count) { this.retryCount = count; }). method('setTimeout', function (time) { this.timeout = time; }). method('add', function (o) { this.queue.push(o); }). method('pause', function () { this.paused = true; }). method('dequeue', function () { this.queue.pop(); }). method('clear', function () { this.queue = []; });
代碼看起來(lái)很多,折疊以后就可以發(fā)現(xiàn),其實(shí)就是在隊(duì)列上定義了flush, setRetryCount, setTimeout, add, pause, dequeue, 和clear方法。
var q = new DED.Queue; // 設(shè)置重試次數(shù)高一點(diǎn),以便應(yīng)付慢的連接 q.setRetryCount(5); // 設(shè)置timeout時(shí)間 q.setTimeout(1000); // 添加2個(gè)請(qǐng)求. q.add({ method: 'GET', uri: '/path/to/file.php?ajax=true' }); q.add({ method: 'GET', uri: '/path/to/file.php?ajax=true&woe=me' }); // flush隊(duì)列 q.flush(); // 暫停隊(duì)列,剩余的保存 q.pause(); // 清空. q.clear(); // 添加2個(gè)請(qǐng)求. q.add({ method: 'GET', uri: '/path/to/file.php?ajax=true' }); q.add({ method: 'GET', uri: '/path/to/file.php?ajax=true&woe=me' }); // 從隊(duì)列里刪除最后一個(gè)請(qǐng)求. q.dequeue(); // 再次Flush q.flush();
上面的調(diào)用代碼里并沒(méi)有橋接,那橋呢?看一下下面的完整示例,就可以發(fā)現(xiàn)處處都有橋哦:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>Ajax Connection Queue</title> <script src="utils.js"></script> <script src="queue.js"></script> <script type="text/javascript"> addEvent(window, 'load', function () { // 實(shí)現(xiàn). var q = new DED.Queue; q.setRetryCount(5); q.setTimeout(3000); var items = $('items'); var results = $('results'); var queue = $('queue-items'); // 在客戶端保存跟蹤自己的請(qǐng)求 var requests = []; // 每個(gè)請(qǐng)求flush以后,訂閱特殊的處理步驟 q.onFlush.subscribe(function (data) { results.innerHTML = data; requests.shift(); queue.innerHTML = requests.toString(); }); // 訂閱時(shí)間處理步驟 q.onFailure.subscribe(function () { results.innerHTML += ' <span style="color:red;">Connection Error!</span>'; }); // 訂閱全部成功的處理步驟x q.onComplete.subscribe(function () { results.innerHTML += ' <span style="color:green;">Completed!</span>'; }); var actionDispatcher = function (element) { switch (element) { case 'flush': q.flush(); break; case 'dequeue': q.dequeue(); requests.pop(); queue.innerHTML = requests.toString(); break; case 'pause': q.pause(); break; case 'clear': q.clear(); requests = []; queue.innerHTML = ''; break; } }; var addRequest = function (request) { var data = request.split('-')[1]; q.add({ method: 'GET', uri: 'bridge-connection-queue.php?ajax=true&s=' + data, params: null }); requests.push(data); queue.innerHTML = requests.toString(); }; addEvent(items, 'click', function (e) { var e = e || window.event; var src = e.target || e.srcElement; try { e.preventDefault(); } catch (ex) { e.returnValue = false; } actionDispatcher(src.id); }); var adders = $('adders'); addEvent(adders, 'click', function (e) { var e = e || window.event; var src = e.target || e.srcElement; try { e.preventDefault(); } catch (ex) { e.returnValue = false; } addRequest(src.id); }); }); </script> <style type="text/css" media="screen"> body { font: 100% georgia,times,serif; } h1, h2 { font-weight: normal; } #queue-items { height: 1.5em; } #add-stuff { padding: .5em; background: #ddd; border: 1px solid #bbb; } #results-area { padding: .5em; border: 1px solid #bbb; } </style> </head> <body id="example"> <div id="doc"> <h1> 異步聯(lián)接請(qǐng)求</h1> <div id="queue-items"> </div> <div id="add-stuff"> <h2>向隊(duì)列里添加新請(qǐng)求</h2> <ul id="adders"> <li><a href="#" id="action-01">添加 "01" 到隊(duì)列</a></li> <li><a href="#" id="action-02">添加 "02" 到隊(duì)列</a></li> <li><a href="#" id="action-03">添加 "03" 到隊(duì)列</a></li> </ul> </div> <h2>隊(duì)列控制</h2> <ul id='items'> <li><a href="#" id="flush">Flush</a></li> <li><a href="#" id="dequeue">出列Dequeue</a></li> <li><a href="#" id="pause">暫停Pause</a></li> <li><a href="#" id="clear">清空Clear</a></li> </ul> <div id="results-area"> <h2> 結(jié)果: </h2> <div id="results"> </div> </div> </div> </body> </html>
在這個(gè)示例里,你可以做flush隊(duì)列,暫停隊(duì)列,刪除隊(duì)列里的請(qǐng)求,清空隊(duì)列等各種動(dòng)作,同時(shí)相信大家也體會(huì)到了橋接的威力了。
橋接模式的優(yōu)點(diǎn)也很明顯,我們只列舉主要幾個(gè)優(yōu)點(diǎn):
同時(shí)橋接模式也有自己的缺點(diǎn):
大量的類將導(dǎo)致開(kāi)發(fā)成本的增加,同時(shí)在性能方面可能也會(huì)有所減少。
更多建議: