门面模式
"焖面",有两个作用,一是简化类的接口;二是消除类与使用他的业务代码之间的耦合.他几乎是所有 JS 库的核心原则.通过建立一些便利方法可以让复杂系统变得更加简单易用,焖面模式可以使库提供的工具更加容易理解.
焖面可以简化错误记录或者跟踪页面视图统计数据这类这类常用,重复性的任务,通过添加一些方法(是对元有一些方法的组合利用),还可以让对象的功能更完善.
焖面可以简化复杂接口,可以在背后为你进行错误检查,清除不再需要的大对象,以及用一种简单方式展现对象功能.
这是一种组织性的模式,可以用来修改类和对象的接口,使其更易使用.
常见的焖面元素
快捷方式图标,是在引导用户至某个地方的接口,如果没有快捷方式,那些内部深层次的文件或者目录就不太容易查找.
基于 GUI 的 OS 就是计算机数据和功能的一个焖面.每次点击,拖动或者移动东西,实际上是在操作焖面,间接地执行一些背后的命令.
这里有个兼容各种浏览器的事件监听器例子(之前桥接也有个事件监听器例子,但是现在要涉及 addEvent()
函数的定义:
function addEvent(el, type, fn) {
if (window.addEventListener) {
el.addEVentListrner(type, fn, false);
}
else if (window.attachEvent) {
el.attachEvent(on + type, fn);
}
else {
el['on' + type] = fn;
}
}
JS 是一种事件驱动的语言,addEvent 函数是一个基本的焖面,每次为一个元素添加事件监听器时都得针对浏览器间的差异进行检查而烦恼.
为了尽量提高开发效率,简化常见任务,提供兼容每个浏览器各自内置 JS 函数实现的共同接口(这样就会方便许多),诞生了许许多多工具函数集(比如说 loadash),还有Prototype, jQuery, YUI, Vue, React, Angular 等等第三方 JS 库.
焖面的一个好处是对函数的组合--convenience function
:
function a(x) {
...
}
function b(y) {
...
}
function ab(x, y) {
a(x);
b(y);
}
之所以依然保留前两个函数,是出于细粒度和灵活性的考虑.
看起来有点类似于桥接模式连接多个类...不过不能混淆,这里的焖面仅仅是对函数的组合,没有涉及到类.
用 DOM 脚本编程经常用到的两个普通事件方法为例:
event.stopPropagation()
event.preventDefault()
前者用来中断事件沿着 DOM 树向上冒泡传播,后者是阻止浏览器针对一个事件的默认行为.比如说防止浏览器链接点击跳转新页面,还可以用来阻止表单的提交.
不同浏览器为这两个功能提供的接口略有差异,所以:
var DED = window.DED || {};
DED.util = {
stopPropagation: function (e) {
if (ev.stopPropagation) {
// W3 interface
e.stopPropagation();
}
else {
// IE's interface
e.cancelBubble = true;
}
},
preventDefault: function (e) {
if (e.preventDefault) {
// W3 interface
e.preventDefault();
}
else {
// IE's interface
e.returnValue = false;
}
},
// our convience method.
stopEvent: function (e) {
DED.util.stopPropagation(e);
DED.util.preventDefault(e);
}
}
焖面和适配器看起来很像,但是实际上,适配器是一种包装器,对接口进行适配以便在不兼容系统中使用它,而创建焖面元素主要是方便起见,提供一个简化的接口,并不适合于需要特定接口的系统.
设置 HTML 元素样式
现在要用原生 js一次设置几个元素的某个样式,很合理:
var ele1 = document.getElementById('foo');
ele1.style.color = '#f00';
var ele2 = document.getElementById('foo');
ele2.style.color = '#f00';
var ele3 = document.getElementById('foo');
ele3.style.color = '#f00';
不停地写 getElementById 并且为每个元素设置同样的属性和数值肯定就不对,这时候用上焖面,创建一个接口,简化成批设置元素样式的工作.现在我们逆向思维:先想象一下如何最方便地使用该方法,然后再编写方法源码本身:
setCSS(['foo'], {
position: 'absolute',
top: '50px',
left: '300px'
});
function serCSS(el, styles) {
for (var prop in styles) {
if (el.hasOwnProperty(prop)) continue;
setStyle(el, prop, styles[prop]);
}
}
设计一个事件工具
在处理浏览器的开发问题时,最好创建一些焖面函数,如果要设计一个大型库,最好把其中所有的工具元素拢在一起,这样更好用,访问起来也简单.鉴于各种浏览器在事件处理方面表现出来的大量差异,开发一个事件工具很有必要.
要用到单体模式,位于DED.Util 命名空间中,包含着各个静态方法,比如说,怎样获得事件目标元素和事件对象,还有如何处理事件传播和事件默认行为:
DED.Util.Event = {
getEvent: function (e) {
return e || window.event;
},
getTarget: function (e) {
return e.target || e.srcElement;
},
stopPropagation: function (e) {
if (e.stopPropagation) {
e.stopPropagation();
}
else {
e.cancelBubble = true;
}
},
preventDefault: function (e) {
if (e.preventDefault) {
e.preventDefault();
}
else {
e.returnValue = false;
}
},
stopEvent: function (e) {
this.stopPropagation(e);
this.preventDefault(e);
}
};
焖面模式的适用场合
判断是否应该应用焖面模式的关键在于辨认那些反复成组出现的代码,如果两个函数经常一块出现,那么或许可以可以考虑写一个组合式的焖面函数
;
在核心工具代码中使用焖面函数的另一个目的是应对 js 内置函数在不同浏览器中的不同表现.这样做并不是因为不能直接使用这些 API,而是因为在处理浏览器差异问题时最好的解决办法是把这些差异抽取到焖面方法中
,比如说addEvent 函数可以提供一个更一致的接口.
利
编写一次组合代码,然后可以反复使用,并且提供了一个处理常见问题和任务的简化接口,也提供了较高层的功能,降低了对外部代码的依赖程度,为应用系统的开发增加了一些额外的灵活性.通过使用焖面模式,可以避免与下层子系统紧密耦合,可以对这个系统进行修改而不会影响到业务代码.
弊
焖面模式经常会被滥用,相比一个庞杂的焖面函数,一个更简单的组成函数在粒度方面更有吸引力.
适配器
它用来在现有接口和不兼容的类之间进行适配.使用这种模式的对象叫做包装器 wrapper,他们是在用一个新的接口包装另一个对象.在设计类的时候往往会遇到有些接口不能与现有 API 一同使用的情况,借助于适配器,不用直接修改这些类也能使用它们.现在将考察这类场合,并探讨用适配器模连接对象的各种方式.
适配器特点
适配器可以被添加到现有代码中以协调两个不同的接口.如果现有代码的接口能很好地满足需要,那么可能没有必要使用适配器.但是如果现有接口对于手头的需求来说不够直观或者实用,那么可以使用适配器来提供一个更简洁或者更丰富的接口.
从表面上看,适配器模式很想焖面模式,他们都要对别的对象进行包装并且改变其呈现的接口,差别在于如何改变接口.焖面元素展现的是一个简化的接口,它并不提供额外的选择,而且有时候为了方便完成常见任务他还会做出一些假定;而适配器则要把一个接口转换另一个接口,他并不会滤除某些能力,也不会简化接口.
适配器可以被实现为不兼容方法调用之间的一个代码薄层.如果你有一个居右3个字符串参数的函数,但是客户系统拥有的是一个包含3个字符串元素的数组,那么就可以用一个适配器来衔接二者:
var clientObject = {
string1: 'foo',
string2: 'var',
string3: 'baz'
};
function interfaceMedthod(str1, str2, str3) {
...
}
// in order to pass clientObject-param to interfaceMethod-function, we need 适配器:
function clentToInterfaceAdapter(srcObj) {
interfaceMethod(srcObj.string1, srcObj.string2, srcObj.string3);
}
clentToInterfaceAdapter(clientObject);
clientToInterfaceAdapter 函数的作用就在于对 interfaceMethod 函数进行包装,并把传递给它的参数转换为后者需要的形式.
适配器适用场合
适用于客户系统期待接口与现有 API 提供的接口不兼容这种场合.它只能用来协调语法上的差异问题.适配器所适配的两个方法执行的应该是类似的任务,否则的话无法解决问题.
利
有助于避免大规模修改现有业务代码,其工作机制:用一个新的接口对现有类的接口进行包装,这样业务代码就无需大动干戈.
弊
如果需要彻底重写代码,另外适配器也会引入一批不要支持的新工具;
如果现有 API 还未定形或者新接口还没有定形那么适配器不会一直管用;
所以说适配器有可能是一种不必要的开销,不必引入.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。