在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。
软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。
外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。
外观(Facade)模式的主要缺点如下。
外观模式不仅简化类中的接口,而且对接口与调用者也进行了解耦。外观模式经常被认为开发者必备,它可以将一些复杂操作封装起来,并创建一个简单的接口用于调用。
外观模式经常被用于JavaScript类库里,通过它封装一些接口用于兼容多浏览器,外观模式可以让我们间接调用子系统,从而避免因直接访问子系统而产生不必要的错误。
外观模式的优势是易于使用,而且本身也比较轻量级。但也有缺点 外观模式被开发者连续使用时会产生一定的性能问题,因为在每次调用时都要检测功能的可用性。
下面是一段未优化过的代码,我们使用了外观模式通过检测浏览器特性的方式来创建一个跨浏览器的使用方法。
比如对document对象添加click事件的时候:
function addEvent(dom, type, fn) {
if (dom.addEventListener) { // 支持DOM2级事件处理方法的浏览器
dom.addEventListener(type, fn, false)
} else if (dom.attachEvent) { // 不支持DOM2级但支持attachEvent
dom.attachEvent('on' + type, fn)
} else {
dom['on' + type] = fn // 都不支持的浏览器
}
}
const myInput = document.getElementById('myinput')
addEvent(myInput, 'click', function() {console.log('绑定 click 事件')})
还可以用来解决一些其他的浏览器兼容性问题:
const getEvent = function(event) { // 获取事件对象
return event || window.event // IE下window.event
}
const getTarget = function(event) { // 获取事件元素
const event = getEvent(event)
return event.target || event.srcElement // IE下event.srcElement
}
const preventDefault = function(event) { // 阻止默认事件
const event = getEvent(event)
if (event.preventDefault) {event.preventDefault()}
else {event.returnValue = false} // IE下
}
const cancelBubble = function(event) {
const event = getEvent(event)
if (event.stopPropagation) {event.stopPropagation()}
else {event.cancelBubble = true} // IE下
}
document.onclick = function(e) {
preventDefault(e)
if (getTarget(e) !== document.getElementById('myinput')) {console.log('呵呵')}
}
那么何时使用外观模式呢?一般来说分三个阶段: