本文深入解析JavaScript中的Facade(外观)设计模式。通过对比适配器模式,阐明其提供高层简化接口的核心目的。结合事件监听、DOM样式设置等具体代码示例,展示如何利用外观模式隐藏复杂实现细节、降低模块耦合度,并讨论其在提升代码可维护性与避免过度封装之间的权衡。
外观模式概念解析
概念
外观模式(Facade Pattern),又称门面模式,是一种结构相对简单但应用广泛的设计模式。它通过提供一个高层接口,简化了客户端与子系统之间的交互,使得调用过程更加直观和便捷。
需要明确的是,外观模式与适配器模式有着本质区别。适配器模式主要作为一种包装器,用于将不兼容的接口转换为客户端期望的特定接口,以解决兼容性问题。而外观模式的核心目的在于简化调用流程,它并不旨在改变接口以适配特定系统,而是致力于提供一个更简洁、易用的统一接口。
JavaScript代码示例
JavaScript代码示例
用一段再简单不过的代码来表示
var getName = function(){
return ''svenzeng"
}
var getSex = function(){
return 'man'
}
假设你需要分别调用 getName 和 getSex 函数来获取用户信息。此时,可以创建一个更高层级的接口 getUserInfo,由它内部调用上述两个函数,从而为调用者提供统一的访问点。
var getUserInfo = function(){
var info = a() + b();
return info;
}
也许你会问,为什么一开始不把 getName 和 getSex 的代码合并在一起,比如直接写成一个函数?
var getNameAndSex = function(){
return 'svenzeng" + "man";
}
答案显而易见。这就好比食堂的炒菜师傅,不会因为顾客同时点了烧鸭和白菜,就将两者混在一个锅里炒制。相反,师傅更倾向于提供一份“烧鸭饭套餐”。在程序设计中,我们需要保证函数或对象保持在合理的粒度范围内,因为并非所有功能都适合捆绑在一起。此外,外观模式还能对用户隐藏真正的实现细节,用户只需关注最高层的接口。例如在烧鸭饭的例子中,用户无需关心师傅是先做烧鸭还是先炒白菜,更无需知晓鸭子来源何处。最后,我们来看一个大家熟悉的外观模式应用案例。
var stopEvent = function( e ){ //同时阻止事件默认行为和冒泡
e.stopPropagation();
e.preventDefault();
}
我知道外观模式的概念相对容易掌握,甚至可能不需要JavaScript代码示例就能理解,但总有一部分开发者更倾向于通过代码来加深印象,认为这样更具说服力。毕竟,没有代码示例的JavaScript文章往往缺乏可信度。我们从最简单的事件监听器处理开始。众所周知,添加事件监听器并非易事,除非你只希望代码在少数几个浏览器上运行。为了确保代码在不同浏览器中正常运行,开发者不得不测试多种方法。在这个示例中,我们将特性检测封装到该方法中:
function addEvent(element, type, func) {
if (window.addEventListener) {
element.addEventListener(type, func, false);
}
else if (window.attachEvent) {
element.attachEvent('on'+type, func);
}
else {
element['on'+type] = func;
}
}
简单吧!我真希望我可以不用写那些不必要的代码,让它们越简单越好,但是如果真是这样就没什么意思了,你也不会想读下去了,对吧?所以我不这么认为,我想我要给你看点更复杂的东西。我只是想说,你的代码原本看起来会有些像下面这样:
var foo = document.getElementById('foo');
foo.style.color = 'red';
foo.style.width = '150px';
var bar = document.getElementById('bar');
bar.style.color = 'red';
bar.style.width = '150px';
var baz = document.getElementById('baz');
baz.style.color = 'red';
baz.style.width = '150px';
太蹩脚了!你对每个元素做了一模一样的事!我认为我们可以让它变得更简单点:
function setStyle(elements, property, value) {
for (var i=0, length = elements.length; i < length; i++) {
document.getElementById(elements[i]).style[property] = value;
}
}
// 现在你可以这么写:
setStyle(['foo', 'bar', 'baz'], 'color', 'red');
setStyle(['foo', 'bar', 'baz'], 'width', '150px');
是不是觉得咱们NB坏了?你快算了吧!咱们可是JavaScript程序员呀!能不能用点脑子,来点真格的。也许我们可以只调用一次就能设置所有的样式。看下这个:
function setStyles(elements, styles) {
for (var i=0, length = elements.length; i < length; i++) {
var element = document.getElementById(elements[i]);
for (var property in styles) {
element.style[property] = styles[property];
}
}
}
//现在你只要这样写:
setStyles(['foo', 'bar', 'baz'], {
color: 'red',
width: '150px'
});
如果我们有很多元素想设置相同的样式,那这段代码真是为我们节省了不少时间。
外观模式的利弊分析
外观模式之利: 使用外观模式的目的就是要让程序员过的更轻松一些,编写一次组合代码,然后就可以反复使用它,这有助于节省时间和精力。给一些复杂的问题提供一个简化接口。
外观方法方便了开发人员,斌共提供过了比较高层的功能,降低对外部代码的依赖程度,为应用系统的开发增加了一些额外的灵活性。通过使用外观模式,可以避免与下层子系统紧密耦合。这样就可以对这个系统进行修改而不会影响到客户代码。
外观模式之弊: 有时候外观元素也会带来一些不必要的额外负担。在实施一些套路之前应该认真掂量一下其实用性。有时相比一个庞杂的外观函数,其组成函数在力度方面更有吸引力。这是因为外观函数可能常常会执行一些你并不需要的任务。
对于简单的个人网站或少量营销网页来说,仅为工具提示和弹出式窗口这样一点增强行为就导入这个Javascript库可能并不明智。此时考虑只使用少许简单的外观元素而不是一个满是这类东西的库。
外观函数为执行各种复杂任务提供了一个简单的接口,它们使代码更容易维护和理解。它们还能弱化子系统和客户代码的耦合。把经常相伴出现的常用函数组合在一起。这个模式在DOM脚本编程这种需要面对葛洪不一致的浏览器接口的环境中很常用。