5分钟读书笔记之 - 设计模式 - 桥接模式
2014-07-21 10:29 sai.zhao 阅读(207) 评论(0) 编辑 收藏 举报补充一点知识:
私有变量 在对象内部使用'var'关键字来声明,而且它只能被私有函数和特权方法访问。
私有函数 在对象的构造函数里声明(或者是通过var functionName=function(){...}来定义),它能被特权函数调用(包括对象的构造函数)和私有函数调用。
特权方法 通过this.methodName=function(){...}来声明而且可能被对象外部的代码调用。可以使用:this.特权函数() 方式来调用特权函数,使用 :私有函数()方式来调用私有函数。
公共属性 通过this.variableName来定义而且在对象外部是可以读写的。不能被私有函数所调用。
公共方法 通过ClassName.prototype.methodName=function(){...}来定义而且可以从对象外部来调用。
原型属性 通过ClassName.prototype.propertyName=someValue来定义。
静态属性 通过ClassName.propertyName=someValue来定义。
在设计一个JS 的 API的时候,可以使用桥接模式来弱化它与使用它的类和对象之间的耦合。 按照GOF的定义,桥接模式的作用在于将抽象与其实现隔离开来,以便二者独立变化。
桥接模式最常见的应用场合之一是事件监听回调函数。
例子:
addEvent(element,'click',getBeerById); function getBeerById(e){ var id =this.id; asyncRequest('GET','url',function(resp){ console.log(resp.responseText) }) }
如果你要对这个API函数做单元测试,或者在命令环境中执行它,显然具有一定的难度(why?下面有解释!)。
对于API开发来说,最好从一个优良的API开始,不要把它与任何特定的实现搅合在一起,修改如下:
function getBeerById(id,callback){ asyncRequest('GET','url'+id ,function(resp){ callback(resp.responseText) }) }
这个版本看起来更实用。现在我们将针对接口而不是实现进行编程,用桥接模式把抽象隔离开来。
addEvent(element,'click',getBeerById); function getBeerByIdBridge(e){ getBeerById(this.id,callback); }
有了这层桥接元素,这个API的适用范围大大拓宽了。因为现在getBeerById并没有和事件对象绑定在一起,你可以在单元测试中运行这个API。解释了(如果你要对这个API函数做单元测试,或者在命令环境中执行它,显然具有一定的难度。)这句话。
除了在事件回调函数与接口之间进行桥接外,桥接模式也可以用于连接公开的API代码和私用的代码实现。此外,它还可以把多个类连接在一起。
桥接函数可以作为特权函数。特权函数可以看开始的定义。
var Public = function(){ var secret = 3; this.privilegedGetter = function(){ return secret; } } var o = new Public; var data = o.privilegedGetter();
用桥接模式可以连接多个类:
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) }
下面,我们要构建一个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(0); } } },50); }
//私有方法,获取XHR 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){ //TODO handle the exception } } } 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; } })() // 添加俩个新方法 Function.prototype.method = function(name,fn){ this.prototype[name]=fn; return this; }; 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 = []; 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 _this = this; this.currentRetry++; var abort = function(){ _this.conn.abort(); if(_this.currentRetry == _this.retryCount){ _this.onFailure.fire(); _this.currentRetry = 0; }else{ _this.flush(); } } this.timer = window.setTimeout(abort,this.timeout); var callback = function(o){ window.clearTimeout(_this.timer); _this.currentRetry = 0; _this.queue.shift(); _this.onFlush.fire(o.responseText); if(_this.queue.length == 0){ _this.onComplete.fire(); return; } _this.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(o){ this.paused = true; }).method('dequeue',function(o){ this.queue.pop(); }).method('clear',function(o){ this.queue=[]; })
队列的实现如下:
var q = new DED.Queue; q.setRetryCount(5); q.setTimeout(1000); 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.flush(); q.pause(); q.clear(); 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(); q.flush();
下面是客户端代码的实现:
addEvent('window','load',function(){ var q = new DED.Queue(); q.retryCount(5); q.setTimeout(3000); var items = $("items") var result = $("results"); var queue = $("queue-items"); var requests = []; q.onFlush.subscribe(function(data){ result.innerHTML = data; requests.shift(); queue.innerHTML =requests.toString(); }); q.onFailure.subscribe(function(){ result.innerHTML +='Conection Error!'; }); q.onComplete.subscribe(function(){ result.innerHTML +='Completed!' }); 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); }) });
既然要设计一个无可挑剔的队列接口,就得在所有适当的地方用上桥接模式。 最明显的是事件监听回调函数并不直接与队列打交道,而是使用了桥接函数,这些桥接函数控制着动作工厂并料理数据输入事宜。
判断什么地方应该使用桥接模式很简单,假如有如下的代码:
$('example').onclick = function(){ new RichTextEditor(); }
你无法看出那个编辑器要显示在什么地方,他有些什么参数。这里的要诀就是让接口可桥接。 把抽象和实现隔离,有助于独立的管理软件各个部分,说到底,桥接元素应该是粘合每一个抽象的粘合因子。