深入理解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 相关文章推荐
Jquery中对数组的操作代码
Aug 12 Javascript
jQuery在IE下使用未闭合的xml代码创建元素时的Bug介绍
Jan 10 Javascript
JS 添加网页桌面快捷方式的代码详细整理
Dec 27 Javascript
Jquery原生态实现表格header头随滚动条滚动而滚动
Mar 18 Javascript
jQuery热气球动画半透明背景的后台登录界面代码分享
Aug 28 Javascript
jQuery实现连续动画效果实例分析
Oct 09 Javascript
jQuery对checkbox 复选框的全选全不选反选的操作
Aug 09 Javascript
js实现百度地图定位于地址逆解析,显示自己当前的地理位置
Dec 08 Javascript
vue.js如何将echarts封装为组件一键使用详解
Oct 10 Javascript
node.js用fs.rename强制重命名或移动文件夹的方法
Dec 27 Javascript
AngularJS下$http服务Post方法传递json参数的实例
Mar 29 Javascript
Angular7.2.7路由使用初体验
Mar 01 Javascript
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
PHP中使用strpos函数实现屏蔽敏感关键字功能
2014/08/21 PHP
Laravel5中contracts详解
2015/03/02 PHP
PHP实现对xml的增删改查操作案例分析
2017/05/19 PHP
使用PHP连接数据库_实现用户数据的增删改查的整体操作示例
2017/09/01 PHP
php使用socket调用http和smtp协议实例小结
2019/07/26 PHP
jQuery ready函数滥用分析
2011/02/16 Javascript
Extjs中ComboBox加载并赋初值的实现方法
2012/03/22 Javascript
输入自动提示搜索提示功能的使用说明:sugggestion.txt
2013/09/02 Javascript
基于JS判断iframe是否加载成功的方法(多种浏览器)
2016/05/13 Javascript
js实现页面a向页面b传参的方法
2016/05/29 Javascript
JavaScript兼容性总结之获取非行间样式案例
2016/08/07 Javascript
JS取数字小数点后两位或n位的简单方法
2016/10/24 Javascript
基于vue实现多引擎搜索及关键字提示
2017/03/16 Javascript
浅谈事件冒泡、事件委托、jQuery元素节点操作、滚轮事件与函数节流
2017/07/22 jQuery
全面解析jQuery中的$(window)与$(document)的用法区别
2017/08/15 jQuery
使用html+js+css 实现页面轮播图效果(实例讲解)
2017/09/21 Javascript
JS解决position:sticky的兼容性问题的方法
2017/10/17 Javascript
js中int和string数据类型互相转化实例
2019/01/16 Javascript
微信小程序五子棋游戏AI实现方法【附demo源码下载】
2019/02/20 Javascript
Node.js中文件系统fs模块的使用及常用接口
2020/03/06 Javascript
[11:27]《一刀刀一天》之DOTA全时刻20:TI4总奖金突破920W TS赛事分析
2014/06/18 DOTA
[00:32]2018DOTA2亚洲邀请赛VGJ.T出场
2018/04/03 DOTA
python实现一个简单的并查集的示例代码
2018/03/19 Python
如何利用python查找电脑文件
2018/04/27 Python
python画一个玫瑰和一个爱心
2020/08/18 Python
Python2和Python3.6环境解决共存问题
2018/11/09 Python
python 在指定范围内随机生成不重复的n个数实例
2019/01/28 Python
python 判断txt每行内容中是否包含子串并重新写入保存的实例
2020/03/12 Python
Python通过字典映射函数实现switch
2020/11/06 Python
清明节演讲稿
2014/05/27 职场文书
电子信息工程专业自荐书
2014/06/24 职场文书
2015羊年春节慰问信
2015/02/14 职场文书
干货分享:推荐信写作技巧!
2019/06/21 职场文书
python的netCDF4批量处理NC格式文件的操作方法
2022/03/21 Python
MySQL详解进行JDBC编程与增删改查方法
2022/06/16 MySQL
go goth封装第三方认证库示例详解
2022/08/14 Golang