深入理解JavaScript系列(44):设计模式之桥接模式详解

介绍

桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化。

正文

桥接模式最常用在事件监控上,先看一段代码

代码如下:
上述代码,有个问题就是getBeerById必须要有浏览器的上下文才能使用,因为其内部使用了this.id这个属性,如果没用上下文,那就歇菜了。所以说一般稍微有经验的程序员都会将程序改造成如下形式:
代码如下:
实用多了,对吧?首先ID可以随意传入,而且还提供了一个callback函数用于自定义处理函数。但是这个和桥接有什么关系呢?这就是下段代码所要体现的了:
代码如下:
这里的getBeerByIdBridge就是我们定义的桥,用于将抽象的click事件和getBeerById连接起来,同时将事件源的ID,以及自定义的call函数(console.log输出)作为参数传入到getBeerById函数里。

这个例子看起来有些简单,我们再来一个复杂点的实战例子。

实战XHR连接队列

我们要构建一个队列,队列里存放了很多ajax请求,使用队列(queue)主要是因为要确保先加入的请求先被处理。任何时候,我们可以暂停请求、删除请求、重试请求以及支持对各个请求的订阅事件。

基础核心函数 在正式开始之前,我们先定义一下核心的几个封装函数,首先第一个是异步请求的函数封装:

代码如下:

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,true); handleReadyState(http,callback); http.send(postData || null); return http; }; })();

上述封装的自执行函数是一个通用的Ajax请求函数,相信属性Ajax的人都能看懂了。

接下来我们定义一个通用的添加方法函数)的方法

代码如下:
最后再添加关于数组的2个方法,一个用于遍历,一个用于筛选:
代码如下:

if (!Array.prototype.filter) { Array.method('filter',thisObj) { var scope = thisObj || window; var a = []; for (var i = 0,len = this.length; i < len; ++i) { if (!fn.call(scope,this)) { continue; } a.push(this[i]); } return a; }); }

因为有的新型浏览器已经支持了这两种功能(或者有些类库已经支持了),所以要先判断,如果已经支持的话,就不再处理了。

观察者系统

观察者在队列里的事件过程中扮演着重要的角色,可以队列处理时(成功、失败、挂起)订阅事件:
代码如下:

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); } ); } };

队列主要实现代码

首先订阅了队列的主要属性和事件委托:
代码如下:
订阅事件 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的链式调用,则队列上添加了很多可用的方法

代码如下:
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方法

简单调用

代码如下:
次数高一点,以便应付慢的连接 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&woe=me' });

// 从队列里删除最后一个请求. q.dequeue(); // 再次Flush q.flush();

桥接呢?

上面的调用代码里并没有桥接,那桥呢?看一下下面的完整示例,就可以发现处处都有桥哦:

代码如下:
<Meta http-equiv="Content-type" content="text/html; charset=utf-8"> Ajax Connection Queue