外观模式(门面模式),是一种相对简单而又无处不在的模式。——联想现实生活的例子:套餐服务!!!
外观模式(Facade)为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口值得这一子系统更加容易使用。
外观模式不仅简化类中的接口,而且对接口与调用者也进行了解耦。外观模式经常被认为开发者必备,它可以将一些复杂操作封装起来,并创建一个简单的接口用于调用。在JavaScript中,也经常使用对底层结构兼容性做统一的封装,来简化用户的使用。
需求:为页面上一个元素添加一个click事件,实现隐藏功能。
上代码
<div id="panel" style="height: 100px; background: red;"></div>
<script type="text/javascript">
//1、绑定一个点击事件,控制panel的隐藏。
document.onclick = function(e){
e.preventDefault();
if (e.target == document.getElementById("panel")) {
hidePanel();
}
}
function hidePanel(){
alert("隐藏了panel");//没有具体实现。
};
</script>
你觉得上述代码有问题吗?
1、首先,onclick事件是DOM0事件,相当于给document绑定了一个事件方法,假如团队中有人也给时间绑定一个方法,那么,你的事件就被覆盖了。
document.onclick = function(e){
//开发人员重新给document绑定click事件,前面的事件将会被覆盖
}
所以应该用DOM2级事件处理机制程序addEventListener().
2、低版本(IE8以下)IE浏览器处理事件不是 addEventListener(),而是attachEvent().而且事件处理名字也不一样,只能用onclick方法,那么如何处理呢?
我们有什么好方法吗?答案是显而易见的,可以用外观模式来封装他们。
举个例子:饭堂的炒菜师傅不会因为你预定了一份烧鸭和一份白菜就把这两样菜炒在一个锅里。他更愿意给你提供一个烧鸭饭套餐。同样在程序设计中,我们需要保证函数或者对象尽可能的处在一个合理粒度,毕竟不是每个人喜欢吃烧鸭的同时又刚好喜欢吃白菜。
外观模式还有一个好处是可以对用户隐藏真正的实现细节,用户只关心最高层的接口。比如在烧鸭饭套餐的故事中,你并不关心师傅是先做烧鸭还是先炒白菜,你也不关心那只鸭子是在哪里成长的。
外观模式的实现:
function addEvent(dom, type, fn){
if (dom.addEventListener) {
dom.addEventListener(type,fn, false);//W3C浏览器
}else if (dom.attachEvent) {
dom.attachEvent("on"+type, fn);//低版本IE
}else{
dom["on"+type] = fn;//两种方法都不支持的处理。
}
}
这样就可以放心的使用了。
var panel = document.getElementById("panel");
addEvent(panel,"click",function(){
alert("绑定第一个事件");
});
addEvent(panel,"click",function(){
alert("绑定第二个事件");
});
addEvent(panel,"click",function(){
alert("绑定第三个事件");
});
回想之前的代码,我还会发现一些兼容性问题,比如e.target
与e.preventDefault()
,下面我们继续完善代码
//获取事件对象
var getEvent = function(event){
return event || window.event;//标准浏览器返回event,低版本IE返回window.event;
}
//获取元素
var getTarget = function(event){
var event = getEvent(event);
return event.target || event.srcElement;//标准浏览器返回event.target,低版本IE返回event.srcElement;
}
//阻止事件默认行为
var preventDefault = function(event){
var event = getEvent(event);
if (event.preventDefault) {
event.preventDefault();//标准浏览器
}else{
event.returnValue = false;//IE浏览器
}
}
有了上述方法,我们就可以兼容的方式来完善刚开始的代码
document.onclick = function(e){
preventDefault(e);
if (getTarget(e) == document.getElementById("panel")) {
hidePanel();
}
}
function hidePanel(){
alert("隐藏了panel");//没有具体实现。
};
再来一个简单的例子,说白了就是用一个接口封装其它的接口,很多代码库就是通过外观模式来封装多个功能,简化底层代码的操作,比如下方:
var EventUtil = {
getEvent: function(event){ //获取事件对象
return event || window.event; //return event?event:window.event;
},
getTarget = function(event){//获取目标元素
var event = getEvent(event);
return event.target || event.srcElement;
},
preventDefault = function(event){//阻止事件默认行为
var event = getEvent(event);
if (event.preventDefault) {
event.preventDefault();//标准浏览器
}else{
event.returnValue = false;//IE浏览器
}
},
addEvent: function(element, type, fn){//添加事件
if (element.addEventListener) {
element.addEventListener(type,fn, false);//W3C浏览器
}else if (element.attachEvent) {
element.attachEvent("on"+type, fn);//低版本IE
}else{
element["on"+type] = fn;//两种方法都不支持的处理。
}
},
removeHandler: function(element, type, fn){//移除事件
if (element.removeEventListener){
element.removeEventListener(type, fn, false);
} else if (element.detachEvent){
element.detachEvent("on" + type, fn);
} else {
element["on" + type] = null;
}
},
stopPropagation:function(event){//取消事件冒泡
var event = getEvent(event);
if (event.stopPropagation) {
event.stopPropagation();
}else{
event.cancleBubble = true;
}
}
}
这样我们就可以随便的操作自己的代码库了。
EventUtil.addEvent(panel,"click",function(){
alert("绑定第一个事件");
});
那么何时使用外观模式呢?外观模式是对接口方法的外层包装,供上层代码调用。一般来说分三个阶段:
首先,在设计初期,应该要有意识地将不同的两个层分离,比如经典的三层结构,在数据访问层和业务逻辑层、业务逻辑层和表示层之间建立外观Facade。
其次,在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加外观Facade可以提供一个简单的接口,减少他们之间的依赖。
第三,在维护一个遗留的大型系统时,可能这个系统已经很难维护了,这时候使用外观Facade也是非常合适的,为系系统开发一个外观Facade类,为设计粗糙和高度复杂的遗留代码提供比较清晰的接口,让新系统和Facade对象交互,Facade与遗留代码交互所有的复杂工作。