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


Posted in Javascript onMarch 04, 2015

介绍

桥接模式(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.实现细节对客户透明,可以对客户隐藏实现细节。

同时桥接模式也有自己的缺点:

大量的类将导致开发成本的增加,同时在性能方面可能也会有所减少。

Javascript 相关文章推荐
JavaScript中检测变量是否存在遇到的一些问题
Nov 11 Javascript
js通过元素class名字获取元素集合的具体实现
Jan 06 Javascript
jquery使用ajax实现微信自动回复插件
Apr 28 Javascript
jQuery的:parent选择器定义和用法
Jul 01 Javascript
深入理解javascript构造函数和原型对象
Sep 23 Javascript
JavaScript的jQuery库中function的存在和参数问题
Aug 13 Javascript
javascript设计模式之模块模式学习笔记
Feb 15 Javascript
将angular.js项目整合到.net mvc中的方法详解
Jun 29 Javascript
jQuery Form插件使用详解_动力节点Java学院整理
Jul 17 jQuery
详解vue2.6插槽更新v-slot用法总结
Mar 09 Javascript
Vue实现购物车实例代码两则
May 30 Javascript
vue项目中企业微信使用js-sdk时config和agentConfig配置方式详解
Dec 15 Vue.js
JS实现FLASH幻灯片图片切换效果的方法
Mar 04 #Javascript
javascript下拉框选项单击事件的例子分享
Mar 04 #Javascript
js实现仿QQ秀换装效果的方法
Mar 04 #Javascript
深入理解JavaScript系列(43):设计模式之状态模式详解
Mar 04 #Javascript
深入理解JavaScript系列(42):设计模式之原型模式详解
Mar 04 #Javascript
javascript 动态创建表格的2种方法总结
Mar 04 #Javascript
深入理解JavaScript系列(41):设计模式之模板方法详解
Mar 04 #Javascript
You might like
盘点被央视点名过的日本动画电影 一部比一部强
2020/03/08 日漫
php中可能用来加密字符串的函数[base64_encode、urlencode、sha1]
2012/01/16 PHP
常见php数据文件缓存类汇总
2014/12/05 PHP
php生成复杂验证码(倾斜,正弦干扰线,黏贴,旋转)
2018/03/12 PHP
在Z-Blog中运行代码[html][/html](纯JS版)
2007/03/25 Javascript
Javascript 获取链接(url)参数的方法[正则与截取字符串]
2010/02/09 Javascript
js中查找最近的共有祖先元素的实现代码
2010/12/30 Javascript
如何创建一个JavaScript弹出DIV窗口层的效果
2013/09/25 Javascript
获取select元素被选中的文本内容的js代码
2014/01/29 Javascript
jquery+html5时钟特效代码分享(可设置闹钟并且语音提醒)
2020/03/30 Javascript
JS从数组中随机取出几个数组元素的方法
2016/08/02 Javascript
jQuery EasyUI 获取tabs的实例解析
2016/12/06 Javascript
浅谈 Vue 项目优化的方法
2017/12/16 Javascript
你可能不知道的前端算法之文字避让(inMap)
2018/01/12 Javascript
详解微信小程序开发之formId使用(模板消息)
2019/08/27 Javascript
JS实现图片切换特效
2019/12/23 Javascript
有趣的JavaScript隐式类型转换操作实例分析
2020/05/02 Javascript
jQuery+ajax实现用户登录验证
2020/09/13 jQuery
Python pass 语句使用示例
2014/03/11 Python
python3读取excel文件只提取某些行某些列的值方法
2018/07/10 Python
Python脚本修改阿里云的访问控制列表的方法
2019/03/08 Python
Python面向对象程序设计之类的定义与继承简单示例
2019/03/18 Python
用python的turtle模块实现给女票画个小心心
2019/11/23 Python
PyTorch中torch.tensor与torch.Tensor的区别详解
2020/05/18 Python
HTML5拖放功能_动力节点Java学院整理
2017/07/13 HTML / CSS
佳能德国网上商店:Canon德国
2017/03/18 全球购物
Hibernate持久层技术
2013/12/16 面试题
创意活动策划书
2014/01/15 职场文书
体操比赛口号
2014/06/10 职场文书
中班下学期幼儿评语
2014/12/30 职场文书
平遥古城导游词
2015/02/03 职场文书
幼儿园庆元旦主持词
2015/07/06 职场文书
家长会后的感想
2015/08/11 职场文书
php解析非标准json、非规范json的方式实例
2022/05/10 PHP
浅谈Redis变慢的原因及排查方法
2022/06/21 Redis
Zabbix6通过ODBC方式监控Oracle 19C的详细过程
2022/09/23 Servers