Gof的定义,桥接模式的作用在于“将抽象与其实现隔离开来,以便二者独立变化“。这种模式对javascript中常见的事件驱动的编程有裨益。
桥接模式最常见和实际的应用场合之一是事件监听器回调函数。假设有一个名为getBeerById的API函数,它根据一个标识符返回有关某种啤酒的信息。当然,在web应用软件中,你希望在用户执行某种操作时获取这种信息。那个被点击的元素很可能具有啤酒的标识符信息,它可能是作为元素自身的ID保存,也可能是作为别的自定义属性保存。
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);
});
}
可以看出这是一个只能工作在浏览器中的API。根据事件监听器回调函数。在本例中并没有使用这个参数,而只是从this对象获取ID。如果你要对这个API函数做单元测试,或更有甚者,在命令行环境中执行它,那就只有祝你好运了。对于API开发来说,最好从一个优良的API开始,不要把它与任何特定的实现搅在一起。毕竟,我们希望所有人都能获取到啤酒信息:
function getBeerById(id,callback){
//make request for beer by ID, then returnthe bee data.
asyncRequest(‘GET’,’beer.uri?id=’+id,function(resp){
calllback(resp.responseText);
}
}
上面这个版本较之前实用得多。从逻辑上讲,把ID作为参数传递给一个名为getBeerById的函数是合情合理的。正如大多数“getter”函数所做的那样,这里使用了一个回调函数。向服务器请求提供信息时,回应结果总是通过一个回调函数返回。现在,我们将针对接口而不是实现进行编辑,用桥接模式把抽象隔离开来。看看修改后的那个事件监听器:
addEvent(element,’click’,getBeerByIdBridge);
function getBeerByIdBridge(e){
getBeerById(this.id,function(beer){
console.log(“Requested Beer:”+beer);
};
}
有了这层桥接元素,这个API的适用范围大大拓宽了,这给了你更大的设计自由。因为现在getBeerById并没有和事件对象捆绑在一起,你可以在单元测试中运行这个API。只需要提供一个ID和回调函数,此外,你也可以在命令行环境中对这个接口进行快速测试。
除了事件回调函数与接口之间进行桥接外,桥接模式也可以用于连接公开的API代码和私用实现的代码。此外,它还可以用来把多个类联结在一起。从类的角度来看,这意味着把接口作为公开的代码编写,而把类的实现作为私用代码编写。
如果一个公用的接口抽象了一些也许应该属于私用性的较复杂的任务,那么可以使用桥接模式来收集某些私用性的信息。可以用一些具有特殊权利的方法作为桥梁以便访问私用变量空间。这一特例中的桥接性函数又称特权函数
var Public=function(){
var secret = 3;
this.privilegedGetter = function(){
return secret;
}
}
var o = new Public();
var data =o.privilegedGetter();
在现实生活中桥梁可以把多种事物联结起来,在javascript中也是如此:
var Class1 =function(a,b,c){
this.a =a;
this.b = b;
this.c = c;
}
var Class2 =function(d){
this.d = d;
}
var BridgeClass =function(a,b,c,d){
this.one = new Class1(a,b,c);
this.two = new Class2(d);
}
这看起来很像是----适配器。
的确如此。但要注意到本例中实际上并没有客户系统要求提供数据。它只不过是用来接纳大量数据并将其发送给责任方的一种辅助性手段。此外,BridgeClass也不是一个客户系统已经实现的现有接口。引入这个类的目的只不过是要桥接一些类而已。
在本例中我们要构建一个Ajax请求队列。这个对象把请求存储在浏览器的内存中的一个队列化数组。刷新队列时每个请求都会按“先入先出”的顺序被发送给一个后端的web服务。如果次序事关紧要,那么在web应用程序中使用队列化系统是有好处的。另外队列还有一个好处:可以通过从队列中删除请求来实现应用程序“取消”功能。电子邮件程序、富文本编辑器或任何涉及因用户输入引起的频繁动作的系统都是适用的例子。最后,连接队列可以帮助用户克服慢速网络带来的不便,甚至可以允许他们离线工作。当然,要想把请求发给服务器,还是需要重建连接。队列系统开发出来后,我们会找出那些存在强耦合的抽象的部分,然后用桥接模式把抽象与实现分开。
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(vari=0,len=msxml.length;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); retrun http; } })(); 例其支持链式调用 Function.prototype.method= function(name,fn){ this.prototype[name]=fn; return this; } 最后添加的是两个新的数组方法:forEach和filter。这是对Array的原型对象扩展。它们是在javascript1.6语言核心中引入的,而目前大多数浏览器使用的还是1.5版的语言核心。 if(!Array.prototype.forEach){ Array.method(‘forEach’,function(fn,thisObj){ var scope = thisObj || window; for(vari=0,len=this.length;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(vari=0,len=this.length;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 = []; 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(‘flush’,function(){ 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(); } that.flush(); }; this.conn = asyncRequest(this.queue[0][‘method’],this.queue[0][‘uri’],callback,this.queue[0][‘params’]).method(“setRetryCount”,function(conunt){ this.retryCount = count; }).method(“setTimeout”,function(){ this.timeout = time; }).method(“add”,function(o){ this.queue.push(o); }).method(“pause”,function(){ this.pause = true }).method(“dequeue”,function(){ this.queue.pop(); }; }).method(“clear”,function(){ this.queue = []; })添加观察者系统
开发队列的基本框架