代码改变世界

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

你无法看出那个编辑器要显示在什么地方,他有些什么参数。这里的要诀就是让接口可桥接。 把抽象和实现隔离,有助于独立的管理软件各个部分,说到底,桥接元素应该是粘合每一个抽象的粘合因子。