深入理解JavaScript系列(44):设计模式之桥接模式
介绍 桥接模式(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;
}
h3, 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">
<h3>
异步联接请求</h3>
<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队列,暂停队列,删除队列里的请求,清空队列等各种动作,同时相信大家也体会到了桥接的威力了。 总结 桥接模式的优点也很明显,我们只列举主要几个优点:
分离接口和实现部分,一个实现未必不变地绑定在一个接口上,抽象类(函数)的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现,同将抽象和实现也进行了充分的解耦,也有利于分层,从而产生更好的结构化系统。
提高可扩充性
实现细节对客户透明,可以对客户隐藏实现细节。 同时桥接模式也有自己的缺点: 大量的类将导致开发成本的增加,同时在性能方面可能也会有所减少。 同步与推荐 本文已同步至目录索引:深入理解JavaScript系列 深入理解JavaScript系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。
深入理解JavaScript系列(44):设计模式之桥接模式的更多相关文章
- 深入理解JavaScript系列(39):设计模式之适配器模式
介绍 适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作.速成包装 ...
- 深入理解JavaScript系列(38):设计模式之职责链模式
介绍 职责链模式(Chain of responsibility)是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象 ...
- 深入理解JavaScript系列(36):设计模式之中介者模式
介绍 中介者模式(Mediator),用一个中介对象来封装一系列的对象交互.中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互. 主要内容来自:http://www ...
- 深入理解JavaScript系列(30):设计模式之外观模式
介绍 外观模式(Facade)为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口值得这一子系统更加容易使用. 正文 外观模式不仅简化类中的接口,而且对接口与调用者也进行了解耦 ...
- 深入理解JavaScript系列(31):设计模式之代理模式
介绍 代理,顾名思义就是帮助别人做事,GoF对代理模式的定义如下: 代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问. 代理模式使得代理对象控制具体对象的引用.代理几乎可以是任何对 ...
- 深入理解JavaScript系列(25):设计模式之单例模式
介绍 从本章开始,我们会逐步介绍在JavaScript里使用的各种设计模式实现,在这里我不会过多地介绍模式本身的理论,而只会关注实现.OK,正式开始. 在传统开发工程师眼里,单例就是保证一个类只有一个 ...
- 深入理解JavaScript系列(33):设计模式之策略模式(转)
介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很 ...
- 深入理解JavaScript系列(42):设计模式之原型模式
介绍 原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象. 正文 对于原型模式,我们可以利用JavaScript特有的原型继承特性去创建对象的方式,也就是 ...
- 深入理解JavaScript系列(43):设计模式之状态模式
介绍 状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类. 正文 举个例子,就比如我们平时在下载东西,通常就会有好几个状态,比如准备状态(ReadySta ...
随机推荐
- SQL Server 2008R2 附件数据库问题记录
在Sql Server 2008 R2里附加数据库时弹出xxx.mdf拒绝访问的错误 详细错误信息如下: TITLE: Microsoft SQL Server Management Studio-- ...
- Major OSL changes to catch up
flat_map optimization for runtime specialization: https://github.com/imageworks/OpenShadingLanguage/ ...
- Pycharm中安装Pygame并写第一个程序
第一步:打开Pycharm 第二步:点File ->Default Settings->Project Interpreter->点加号 第三步: 搜索Pygame->Inst ...
- 爬虫开发14.scrapy框架之分布式操作
分布式爬虫 一.redis简单回顾 1.启动redis: mac/linux: redis-server redis.conf windows: redis-server.exe redis-wi ...
- Oracle ocp 12c-071最新考试题库及答案-1
choose the best answer: View the Exhibit and examine the structure of the CUSTOMERS table. CUSTOMER_ ...
- ajax的两个重要参数contentType 和dataType
contentType 是入参!!!!!! 是传递给后端参数的格式: contentType : 'application/json;charset=UTF-8', contentType : 'te ...
- Python处理json数据--世界国家维度数据
1.准备国家的json数据 将准备好的json数据放在指定的目录下,此处可以重这里下载 2.测试编写python脚本处理json提取字段值 #coding:utf8 import time, re, ...
- consul部署多台Docker集群
Consul 最近在学习Ocelot,发现里面集成Consul,所有部署一下多机版集群,后来发现网上都是在一台虚拟机中的Docker部署,而且大同小异,没有真正解释清楚. 前提准备 4台Centos虚 ...
- Bootrap 项目实战(微金所前端首页)第二部分(首页源码)
首页源码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...
- 实验吧之deeeeeeaaaaaadbeeeeeeeeeef-200
题目中提示说“图片是正确的吗”,赶紧打开图片,图片显示正常,没啥毛病,那就放到winhex里面,好像它的十六进制格式也蛮标准的,然后它的文本区域有个iphone,这个梗我也是百度才知道的: winhe ...