深入理解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 相关文章推荐
js 处理URL实用技巧
Nov 23 Javascript
JS实现的最简Table选项卡效果
Oct 14 Javascript
jquery判断复选框选中状态以及区分attr和prop
Dec 18 Javascript
借助FileReader实现将文件编码为Base64后通过AJAX上传
Dec 24 Javascript
JavaScript中利用jQuery绑定事件的几种方式小结
Mar 06 Javascript
内容滑动切换效果jquery.hwSlide.js插件封装
Jul 07 Javascript
浅谈javascript中的Function和Arguments
Aug 30 Javascript
使用BootStrapValidator完成前端输入验证
Sep 28 Javascript
简单实现js鼠标跟随效果
Aug 02 Javascript
html中通过JS获取JSON数据并加载的方法
Nov 30 Javascript
vue 2.x 中axios 封装的get 和post方法
Feb 28 Javascript
react native 仿微信聊天室实例代码
Sep 17 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笔记之:AOP的应用
2013/04/24 PHP
Laravel实现用户注册和登录
2015/01/23 PHP
PHP mysqli事务操作常用方法分析
2017/07/22 PHP
PHP面向对象程序设计之对象的遍历操作示例
2019/06/12 PHP
this[] 指的是什么内容 讨论
2007/03/24 Javascript
jQuery 入门讲解1
2009/04/15 Javascript
基于jquery实现的可编辑下拉框实现代码
2014/08/02 Javascript
js绘制圆形和矩形的方法
2015/08/05 Javascript
bootstrap table表格插件使用详解
2017/05/08 Javascript
Angualrjs 表单验证的两种方式(失去焦点验证和点击提交验证)
2017/05/09 Javascript
Vue.js实现一个todo-list的上移下移删除功能
2017/06/26 Javascript
使用Bootstrap + Vue.js实现表格的动态展示、新增和删除功能
2017/11/27 Javascript
JS遍历DOM文档树的方法实例详解
2018/04/03 Javascript
微信小程序自定义toast弹窗效果的实现代码
2018/11/15 Javascript
微信小程序实现页面分享onShareAppMessage
2019/08/12 Javascript
d3.js实现图形拖拽
2019/12/19 Javascript
vue列表数据发生变化指令没有更新问题及解决方法
2020/01/16 Javascript
Ant design vue中的联动选择取消操作
2020/10/31 Javascript
[01:19:46]DOTA2-DPC中国联赛 正赛 SAG vs DLG BO3 第一场 2月28日
2021/03/11 DOTA
Python的MongoDB模块PyMongo操作方法集锦
2016/01/05 Python
Python中遇到的小问题及解决方法汇总
2017/01/11 Python
Python爬虫包BeautifulSoup学习实例(五)
2018/06/17 Python
numpy中loadtxt 的用法详解
2018/08/03 Python
用Python实现二叉树、二叉树非递归遍历及绘制的例子
2019/08/09 Python
Python ATM功能实现代码实例
2020/03/19 Python
Python requests HTTP验证登录实现流程
2020/11/05 Python
飞利浦比利时官方网站:Philips比利时
2016/08/24 全球购物
英国天然宝石首饰购买网站:Gemondo Jewellery
2018/10/23 全球购物
大学生毕业求职找工作的自我评价
2013/09/29 职场文书
献爱心活动总结
2014/05/07 职场文书
中层干部培训方案
2014/06/16 职场文书
不服从上级领导安排的检讨书
2014/09/14 职场文书
2015年世界无车日活动总结
2015/03/23 职场文书
2015年店长工作总结范文
2015/04/08 职场文书
python实现简单的聊天小程序
2021/07/07 Python
Python实现简单的俄罗斯方块游戏
2021/09/25 Python