桥接模式
在上一篇博客中我讨论了单例模式以及如何在JavaScript中使用它。这次我们围绕桥接模式(the Bridge Pattern)展开讨论,为了强调它的重要性我们把它安排在JavaScript设计模式系列的第二篇。
我读过的所有关于桥接模式的解释几乎都是对GoF(the Gang of Four)的直接引用,所以我在想要不我也这么写吧?桥接模式应该是“把抽象概念和具体实现分离开来,让这两部分可以完全独立地变化”。桥接模式在像JavaScript这种事件驱动的应用程序中相当有用。而事实上它是一个完全未被充分利用的设计模式。
事件监听器示例
下面的例子里我会用一些jQuery,所以如果你不知道某个函数的功能或用法,你可以查看jQuery的文档。
下面你将看到的这段代码是使用一个名叫getXById的API方法,当然这个方法的实现很糟糕。我们用一个click事件来确认是哪个元素的id会被发送。getXById自己会获取click元素的id,然后用这个获取到的id发起Ajax向服务端请求X。
getXById = function() {
var id = this.id;
$.ajax({
url:'/getx?id=' + id,
success: function(response) {
console.log(response);
}
});
}
$('someElement').bind('click', getXById);
这段代码如果仅仅只是用在某个特定页面的特定用途倒也没什么,然而它是(据说是)一个API的一部分,所以需要做个大修改。我们要把getXById
从事件监听器和处理Ajax结果中剥离出来:
getXById = function(id, callback) {
$.ajax({
url:'/getx?id=' + id,
success: callback
}
}
getXBridge = function() {
var id = this.id;
getXById(this.id, function() {
console.log(response);
});
}
$('someElement').bind('click', getXBridge);
现在你可以在任何地方使用getXById
并且你可以用返回的X做任何事情。
经典示例
我所说的“经典”有双重含义:这个例子是比较常见的面向对象编程语言的案例,并且它使用了类(译注:即classical里的class)。
JavaScript本身并没有类的概念,但是你可以模拟接口,并使用原型来模拟类。这个例子最早见于《Head First Design Pattern》,那是一个Java版本。实际上它是个无关紧的模式,因为在书的后面都没有提供它的代码实例,所以我只能使用几张示意图(此外我还重画了这些图,就是因为我太聪明了)。
我们的首发产品
首先我们从RemoteControl
接口开始。ToshibaRemote
和SonyRemote
都实现了这个接口并且工作于各自的电视。通过这些代码你可以在任意遥控器上调用on()
,off()
或者是setChannel()
,尽管所有的电视都不一样,但是它们都能正常工作。当你想用桥接模式对控制器做些改进时会发生些什么呢:
现在这些电视继承了一个接口,其他遥控器继承了另一个接口(事实上遥控器只有一个类,因为它只需要一个实现),我们可以通过继承来创建变量,并且保持兼容性。是不是想看看代码是怎么写的?我会给你看一个新版本的桥接模式的代码 ,因为我觉得你没有必要看最原始那个版本。其实我认为你根本不需要看任何代码,但是我敢肯定有些人无论如何就是想看代码。谁让我们是程序员呢?那就给我们看代码!
var RemoteControl = function(tv) {
this.tv = tv;
this.on = function() {
this.tv.on();
};
this.off = function() {
this.tv.off();
};
this.setChannel = function(ch) {
this.tv.tuneChannel(ch);
};
};
/* 更新,更好的遥控器 */
var PowerRemote = function(tv) {
this.tv = tv;
this.currChannel = 0;
this.setChannel = function(ch) {
this.currChannel = ch;
this.tv.tuneChannel(ch);
};
this.nextChannel = function() {
this.setChannel(this.currChannel + 1);
};
this.prevChannel = function() {
this.setChannel(this.currChannel - 1);
};
};
PowerRemote.prototype = new RemoteControl();
/** TV 接口
因为JavaScript里没有接口的概念,
所以我只是使用一些注释来定义应该实现些什么
function on
function off
function tuneChannel(channel)
*/
/* Sony TV */
var SonyTV = function() {
this.on = function() {
console.log('Sony TV is on');
};
this.off = function() {
console.log('Sony TV is off');
};
this.tuneChannel = function(ch) {
console.log('Sony TV tuned to channel ' + ch);
};
}
/* Toshiba TV */
var ToshibaTV = function() {
this.on = function() {
console.log('Welcome to Toshiba entertainment');
};
this.off = function() {
console.log('Goodbye Toshiba user');
};
this.tuneChannel = function(ch) {
console.log('Channel ' + ch + ' is set on your Toshiba television');
};
}
/* 让我们看看它是如何操作的 */
var sony = new SonyTV(),
toshiba = new ToshibaTV(),
std_remote = new RemoteControl(sony),
pwr_remote = new PowerRemote(toshiba);
std_remote.on(); // prints "Sony TV is on"
std_remote.setChannel(55); // prints "Sony TV tuned to channel 55"
std_remote.setChannel(20); // prints "Sony TV tuned to channel 20"
std_remote.off(); // prints "Sony TV is off"
pwr_remote.on(); // prints "Welcome to Toshiba entertainment"
pwr_remote.setChannel(55); // prints "Channel 55 is set on your Toshiba television"
pwr_remote.nextChannel(); // prints "Channel 56 is set on your Toshiba television"
pwr_remote.prevChannel(); // prints "Channel 55 is set on your Toshiba television"
pwr_remote.off(); // prints "Goodbye Toshiba user"
上面就是JavaScript里的桥接模式。