深入理解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 不只是脚本
May 30 Javascript
如何让DIV可编辑、可拖动示例代码
Sep 18 Javascript
jquery属性选择器not has怎么写 行悬停高亮显示
Nov 13 Javascript
JS使用ajax从xml文件动态获取数据显示的方法
Mar 24 Javascript
jquery实现可关闭的倒计时广告特效代码
Sep 02 Javascript
JS实现滑动菜单效果代码(包括Tab,选项卡,横向等效果)
Sep 24 Javascript
JavaScript中的Reflect对象详解(ES6新特性)
Jul 22 Javascript
基于js实现二级下拉联动
Dec 17 Javascript
vue引入jq插件的实例讲解
Sep 12 Javascript
vue中配置mint-ui报css错误问题的解决方法
Oct 11 Javascript
react 父子组件之间通讯props
Sep 08 Javascript
学习jQuery中的noConflict()用法
Sep 28 jQuery
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
一条久听不愿放下的DIY森海MX500,三言两语话神奇
2021/03/02 无线电
ThinkPHP3.1新特性之字段合法性检测详解
2014/06/19 PHP
Yii2.0中的COOKIE和SESSION用法
2016/08/12 PHP
php获得刚插入数据的id 的几种方法总结
2018/05/31 PHP
php实现数组重复数字统计实例
2018/09/30 PHP
php使用goto实现自动重启swoole、reactphp、workerman服务的代码
2020/04/13 PHP
超越Jquery_01_isPlainObject分析与重构
2010/10/20 Javascript
js获取dom的高度和宽度(可见区域及部分等等)
2013/06/13 Javascript
HTTP 304错误的详细讲解
2013/11/13 Javascript
JS控制图片翻转示例代码(兼容firefox,ie,chrome)
2013/12/19 Javascript
JavaScript实现的GBK、UTF8字符串实际长度计算函数
2014/08/27 Javascript
javascript框架设计读书笔记之字符串的扩展和修复
2014/12/02 Javascript
JavaScript中iframe实现局部刷新的几种方法汇总
2016/01/06 Javascript
js拖拽功能实现代码解析
2016/11/28 Javascript
使用vue实现点击按钮滑出面板的实现代码
2017/01/10 Javascript
vue构建单页面应用实战
2017/04/10 Javascript
jQuery Form插件使用详解_动力节点Java学院整理
2017/07/17 jQuery
微信网页授权并获取用户信息的方法
2018/07/30 Javascript
js实现无缝轮播图
2020/03/09 Javascript
es6函数之尾调用优化实例分析
2020/04/25 Javascript
Vue 组件的挂载与父子组件的传值实例
2020/09/02 Javascript
Vue toFixed保留两位小数的3种方式
2020/10/23 Javascript
[32:36]完美世界DOTA2联赛PWL S3 LBZS vs CPG 第二场 12.12
2020/12/16 DOTA
Flask框架学习笔记(一)安装篇(windows安装与centos安装)
2014/06/25 Python
简单介绍Python中的decode()方法的使用
2015/05/18 Python
用pandas按列合并两个文件的实例
2018/04/12 Python
Python输出指定字符串的方法
2020/02/06 Python
three.js模拟实现太阳系行星体系功能
2019/09/03 HTML / CSS
数百万免费的图形资源:Freepik
2020/09/21 全球购物
业务代表的岗位职责
2013/11/16 职场文书
党员个人思想汇报
2013/12/28 职场文书
小学兴趣小组活动总结
2014/07/07 职场文书
英文产品推荐信
2015/03/27 职场文书
2015年人力资源部工作总结
2015/04/30 职场文书
基于Python实现nc批量转tif格式
2022/08/14 Python
详解Anyscript开发指南绕过typescript类型检查
2022/09/23 Javascript