## 介紹
橋接模式(Bridge)將抽象部分與它的實現部分分離,使它們都可以獨立地變化。
## 正文
橋接模式最常用在事件監控上,先看一段代碼:
~~~
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);
});
}
~~~
上述代碼,有個問題就是getBeerById必須要有瀏覽器的上下文才能使用,因為其內部使用了this.id這個屬性,如果沒用上下文,那就歇菜了。所以說一般稍微有經驗的程序員都會將程序改造成如下形式:
~~~
function getBeerById(id, callback) {
// 通過ID發送請求,然后返回數據
asyncRequest('GET', 'beer.uri?id=' + id, function(resp) {
// callback response
callback(resp.responseText);
});
}
~~~
實用多了,對吧?首先ID可以隨意傳入,而且還提供了一個callback函數用于自定義處理函數。但是這個和橋接有什么關系呢?這就是下段代碼所要體現的了:
~~~
addEvent(element, 'click', getBeerByIdBridge);
function getBeerByIdBridge (e) {
getBeerById(this.id, function(beer) {
console.log('Requested Beer: '+beer);
});
}
~~~
這里的getBeerByIdBridge就是我們定義的橋,用于將抽象的click事件和getBeerById連接起來,同時將事件源的ID,以及自定義的call函數(console.log輸出)作為參數傳入到getBeerById函數里。
這個例子看起來有些簡單,我們再來一個復雜點的實戰例子。
## 實戰XHR連接隊列
我們要構建一個隊列,隊列里存放了很多ajax請求,使用隊列(queue)主要是因為要確保先加入的請求先被處理。任何時候,我們可以暫停請求、刪除請求、重試請求以及支持對各個請求的訂閱事件。
### 基礎核心函數
在正式開始之前,我們先定義一下核心的幾個封裝函數,首先第一個是異步請求的函數封裝:
~~~
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;
};
})();
~~~
上述封裝的自執行函數是一個通用的Ajax請求函數,相信屬性Ajax的人都能看懂了。
接下來我們定義一個通用的添加方法(函數)的方法:
~~~
Function.prototype.method = function (name, fn) {
this.prototype[name] = fn;
return this;
};
~~~
最后再添加關于數組的2個方法,一個用于遍歷,一個用于篩選:
~~~
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;
});
}
~~~
因為有的新型瀏覽器已經支持了這兩種功能(或者有些類庫已經支持了),所以要先判斷,如果已經支持的話,就不再處理了。
### 觀察者系統
觀察者在隊列里的事件過程中扮演著重要的角色,可以隊列處理時(成功、失敗、掛起)訂閱事件:
~~~
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);
}
);
}
};
~~~
### 隊列主要實現代碼
首先訂閱了隊列的主要屬性和事件委托:
~~~
DED.Queue = function () {
// 包含請求的隊列.
this.queue = [];
// 使用Observable對象在3個不同的狀態上,以便可以隨時訂閱事件
this.onComplete = new DED.util.Observer;
this.onFailure = new DED.util.Observer;
this.onFlush = new DED.util.Observer;
// 核心屬性,可以在外部調用的時候進行設置
this.retryCount = 3;
this.currentRetry = 0;
this.paused = false;
this.timeout = 5000;
this.conn = {};
this.timer = {};
};
~~~
然后通過DED.Queue.method的鏈式調用,則隊列上添加了很多可用的方法:
~~~
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 = [];
});
~~~
代碼看起來很多,折疊以后就可以發現,其實就是在隊列上定義了flush, setRetryCount, setTimeout, add, pause, dequeue, 和clear方法。
### 簡單調用
~~~
var q = new DED.Queue;
// 設置重試次數高一點,以便應付慢的連接
q.setRetryCount(5);
// 設置timeout時間
q.setTimeout(1000);
// 添加2個請求.
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隊列
q.flush();
// 暫停隊列,剩余的保存
q.pause();
// 清空.
q.clear();
// 添加2個請求.
q.add({
method: 'GET',
uri: '/path/to/file.php?ajax=true'
});
q.add({
method: 'GET',
uri: '/path/to/file.php?ajax=true&woe=me'
});
// 從隊列里刪除最后一個請求.
q.dequeue();
// 再次Flush
q.flush();
~~~
### 橋接呢?
上面的調用代碼里并沒有橋接,那橋呢?看一下下面的完整示例,就可以發現處處都有橋哦:
~~~
<!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 () {
// 實現.
var q = new DED.Queue;
q.setRetryCount(5);
q.setTimeout(3000);
var items = $('items');
var results = $('results');
var queue = $('queue-items');
// 在客戶端保存跟蹤自己的請求
var requests = [];
// 每個請求flush以后,訂閱特殊的處理步驟
q.onFlush.subscribe(function (data) {
results.innerHTML = data;
requests.shift();
queue.innerHTML = requests.toString();
});
// 訂閱時間處理步驟
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>
異步聯接請求</h1>
<div id="queue-items">
</div>
<div id="add-stuff">
<h2>向隊列里添加新請求</h2>
<ul id="adders">
<li><a href="#" id="action-01">添加 "01" 到隊列</a></li>
<li><a href="#" id="action-02">添加 "02" 到隊列</a></li>
<li><a href="#" id="action-03">添加 "03" 到隊列</a></li>
</ul>
</div>
<h2>隊列控制</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>
結果:
</h2>
<div id="results">
</div>
</div>
</div>
</body>
</html>
~~~
在這個示例里,你可以做flush隊列,暫停隊列,刪除隊列里的請求,清空隊列等各種動作,同時相信大家也體會到了橋接的威力了。
## 總結
橋接模式的優點也很明顯,我們只列舉主要幾個優點:
1. 分離接口和實現部分,一個實現未必不變地綁定在一個接口上,抽象類(函數)的實現可以在運行時刻進行配置,一個對象甚至可以在運行時刻改變它的實現,同將抽象和實現也進行了充分的解耦,也有利于分層,從而產生更好的結構化系統。
2. 提高可擴充性
3. 實現細節對客戶透明,可以對客戶隱藏實現細節。
同時橋接模式也有自己的缺點:
大量的類將導致開發成本的增加,同時在性能方面可能也會有所減少。
- (1)編寫高質量JavaScript代碼的基本要點
- (2)揭秘命名函數表達式
- (3)全面解析Module模式
- (4)立即調用的函數表達式
- (5)強大的原型和原型鏈
- (6)S.O.L.I.D五大原則之單一職責SRP
- (7)S.O.L.I.D五大原則之開閉原則OCP
- (8)S.O.L.I.D五大原則之里氏替換原則LSP
- (9)根本沒有“JSON對象”這回事!
- (10)JavaScript核心(晉級高手必讀篇)
- (11)執行上下文(Execution Contexts)
- (12)變量對象(Variable Object)
- (13)This? Yes, this!
- (14)作用域鏈(Scope Chain)
- (15)函數(Functions)
- (16)閉包(Closures)
- (17)面向對象編程之一般理論
- (18)面向對象編程之ECMAScript實現
- (19)求值策略
- (20)《你真懂JavaScript嗎?》答案詳解
- (21)S.O.L.I.D五大原則之接口隔離原則ISP
- (22)S.O.L.I.D五大原則之依賴倒置原則DIP
- (23)JavaScript與DOM(上)——也適用于新手
- (24)JavaScript與DOM(下)
- (25)設計模式之單例模式
- (26)設計模式之構造函數模式
- (27)設計模式之建造者模式
- (28)設計模式之工廠模式
- (29)設計模式之裝飾者模式
- (30)設計模式之外觀模式
- (31)設計模式之代理模式
- (32)設計模式之觀察者模式
- (33)設計模式之策略模式
- (34)設計模式之命令模式
- (35)設計模式之迭代器模式
- (36)設計模式之中介者模式
- (37)設計模式之享元模式
- (38)設計模式之職責鏈模式
- (39)設計模式之適配器模式
- (40)設計模式之組合模式
- (41)設計模式之模板方法
- (42)設計模式之原型模式
- (43)設計模式之狀態模式
- (44)設計模式之橋接模式
- (45)代碼復用模式(避免篇)
- (46)代碼復用模式(推薦篇)
- (47)對象創建模式(上篇)
- (48)對象創建模式(下篇)
- (49)Function模式(上篇)
- (50)Function模式(下篇)
- (結局篇)