最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Javascript 桥接模式的理论与实战教程
时间:2015-11-11 编辑:简简单单 来源:一聚教程网
基本理论
桥接模式定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
桥接模式主要有4个角色组成:
(1)抽象类
(2)扩充抽象类
(3)实现类接口
(4)具体实现类
根据javascript语言的特点,我们将其简化成2个角色:
(1)扩充抽象类
(2)具体实现类
怎么去理解桥接模式呢?我们接下来举例说明
桥接模式的实现
理解桥接模式的思想,关键是要理解其分离抽象部分和实现部分的思想。我们举例进行说明
最简单的桥接模式
其实我们最经常用的jQuery的each函数就是一个典型的桥接模式,我们模拟其实现如下:
var each = function (arr, fn) {
for (var i = 0; i < arr.length; i++) {
var val = arr[i];
if (fn.call(val, i, val, arr)) {
return false;
}
}
}
var arr = [1, 2, 3, 4];
each(arr, function (i, v) {
arr[i] = v * 2;
})
在这个例子中,我们通过each函数循环了arr数组,别看这个例子很常见,但其中就包含了典型的桥接模式。
在这个例子中,抽象部分是each函数,也就是上面说的扩充抽象类,实现部分是fn,即具体实现类。抽象部分和实现部分可以独立的进行变化。这个例子虽然简单,但就是一个典型的桥接模式的应用。
插件开发中的桥接模式
桥接模式的一个适用场景是组件开发。我们平时开发组件为了适应不同场合,组件相应的会有许多不同维度的变化。桥接模式就可以应用于此,将其抽象与实现分离,使组件的扩展性更高。
假设我们要开发一个弹窗插件,弹窗有不同的类型:普通消息提醒,错误提醒,每一种提醒的展示方式还都不一样。这是一个典型的多维度变化的场景。首先我们定义两个类:普通消息弹窗和错误消息弹窗。
function MessageDialog(animation) {
this.animation = animation;
}
MessageDialog.prototype.show = function () {
this.animation.show();
}
function ErrorDialog(animation) {
this.animation = animation;
}
ErrorDialog.prototype.show = function () {
this.animation.show();
}
这两个类就是前面提到的抽象部分,也就是扩充抽象类,它们都包含一个成员animation。
两种弹窗通过show方法进行显示,但是显示的动画效果不同。我们定义两种显示的效果类如下:
function LinerAnimation() {
}
LinerAnimation.prototype.show = function () {
console.log("it is liner");
}
function EaseAnimation() {
}
EaseAnimation.prototype.show = function () {
console.log("it is ease");
}
这两个类就是具体实现类,它们实现具体的显示效果。那我们如何调用呢?
var message = new MessageDialog(new LinerAnimation());
message.show();
var error = new ErrorDialog(new EaseAnimation());
error.show();
如果我们要增加一种动画效果,可以再定义一种效果类,传入即可。
总结
学习桥接模式关键是要理解抽象部分与实现部分的分离,使得二者可以独立的变化,而不必拘泥于形式。JS插件灵活的变化,适用场景的多变就非常适合使用这种模式来实现。使用桥接模式最重要的是要找出系统中不同的变化维度。
深入理解JavaScript设计模式之桥接模式
桥接模式最常用在事件监控上,先看一段代码:
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();
桥接呢?
上面的调用代码里并没有桥接,那桥呢?看一下下面的完整示例,就可以发现处处都有桥哦:
"http://www.w3.org/TR/html4/strict.dtd">
在这个示例里,你可以做flush队列,暂停队列,删除队列里的请求,清空队列等各种动作,同时相信大家也体会到了桥接的威力了。
总结
桥接模式的优点也很明显,我们只列举主要几个优点:
分离接口和实现部分,一个实现未必不变地绑定在一个接口上,抽象类(函数)的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现,同将抽象和实现也进行了充分的解耦,也有利于分层,从而产生更好的结构化系统。
提高可扩充性
实现细节对客户透明,可以对客户隐藏实现细节。
同时桥接模式也有自己的缺点:
大量的类将导致开发成本的增加,同时在性能方面可能也会有所减少。
-
下一个: jquery实现九宫格大转盘抽奖源码
相关文章
- 无线网连接提示没有有效的IP配置解决方法 03-08
- javascript实现下雨效果 07-28
- javascript实现圣旨卷轴展开效果(代码分享) 07-20
- JavaScript实现星星等级评价功能 07-14
- javascript实现滑动解锁功能 07-14
- 十大热门的JavaScript框架和库 07-13