1。定义外观模式(FacadePattern),也称为外观模式,是指提供一个统一的接口来访问多个子系统的多个不同接口,为其中一个子系统组接口提供统一的高层接口。它使子系统更易于使用,不仅简化了类中的接口,而且实现了调用者与接口的解耦。2.类图这种设计模式由以下角色组成:外观角色:外观模式的核心。它由熟悉子系统功能的客户端角色调用。几个功能的组合是内部根据客户角色的需要预先确定的子系统角色:实现子系统的功能。它对客户端角色和门面是未知的。子系统作用:实现子系统的功能。对客户角色和Facade是未知的。客户角色通过调用Facede完成要实现的功能。门面模式类图3.生活中常见的空调、冰箱、洗衣机等例子。内部结构并不简单。对于我们使用用户来说,了解其内部运行机制的门槛比较高,但是遥控器/控制面板上那几个按键还是比较容易理解的,这就是外观模式的意义。在类似的场景中,这些示例具有以下特征:统一的外观为复杂的子系统提供简单的高级功能接口。访问者直接调用子系统内部模块造成的复杂引用关系,现在只访问这个统一的表象就可以避免了。4、一般实现Facade模式下,客户端直接连接到Facade,通过接口连接到子接口,子接口中封装的一系列复杂操作不是我们关注的重点。结构如下:外观模式一般作为子系统的功能导出出现,使用时可以向其添加新的功能,但不推荐这样做,因为外观应该是对现有功能的封装,不应该与新功能混合。功能。4.1计算器类Sum{sum(a,b){returna+b;}}classMinus{minus(a,b){返回a-b;}}classMultiply{multiply(a,b){返回a*b;}}类计算器{sumObjminusObjmultiplyObjconstructor(){this.sumObj=newSum();this.minusObj=newMinus();this.multiplyObj=newMultiply();}sum(...args){返回this.sumObj.sum(...args);}minus(...args){returnthis.minusObj.minus(...args);}multiply(...args){返回this.multiplyObj.multiply(...args);}}letcalculator=newCalculator();console.log(calculator.sum(1,2));console.log(calculator.minus(1,2));console.log(计算器.multiply(1,2));语言:c4.2ComputerclassCPU{startup(){console.log('turnonCPU');}shutdown(){console.log('关闭CPU');}}classMemory{startup(){console.log('打开内存');}shutdown(){console.log('关闭内存');}}classDisk{startup(){console.log('打开硬盘');}shutdown(){console.log('关闭硬盘');}}类计算机{cpu;记忆;磁盘;constructor(){this.cpu=newCPU();this.memory=newMemory();this.disk=newDisk();}startup(){this.cpu.startup();这个.memory.startup();这个.disk.startup();}shutdown(){this.cpu.shutdown();这个.memory.shutdown();这个.disk.shutdown();}}letcomputer=newComputer();computer.startup();computer.shutdown();复制代码4.3压缩导出{}varzlib=require('zlib');varfs=require('fs');letpath=require('path');functionopen(input){letext=path.extname(input);switch(ext){case'.gz':returnunZip(input);case'.rar':returnunRar(input);case'.7z':returnun7z(input);默认值:中断;}}functionunZip(src){vargunzip=zlib.createGunzip();varinputStream=fs.createReadStream(srC);varoutputStream=fs.createWriteStream(src.slice(0,-3));console.log('outputStream');inputStream.pipe(gunzip).pipe(outputStream);}functionunRar(src){console.log('Rar解压',src);}functionun7z(src){console.log('7z解压',src);}open('./source.txt.gz');functionzip(src){vargzip=zlib.createGzip();//创建压缩流varinputStream=fs.createReadStream(src);varoutputStream=fs.createWriteStream(src+'.gz');inputStream.pipe(gzip).pipe(outputStream);}zip('source.txt');复制代码5.前端应用场景5.1函数参数重载有一种情况,比如一个函数有多个参数,其中一个可以传也可以不传,你当然可以直接创建两个接口,但是使用方法函数参数重载的方式可以让用户获得更多的自由度,使得两种在使用上基本相似的方法可以获得统一的外观functionbindEvent(elem,type,selector,fn){if(fn===undefined){fn=选择器;选择器=空;}//...剩余相关逻辑}bindEvent(elem,'click','#div1',fn)bindEvent(elem,'click',fn)复制代码在上面的事件绑定函数中,参数选择器是可选的。该方法常用于一些工具库或框架提供的多功能方法中,尤其是通用API的一些参数可传可不传的时候。参数重载后的函数将获得更多的使用自由,而无需重新创建新的API,这在Vue、React、jQuery和Lodash等库中使用非常频繁。5.2polyfill平滑浏览器兼容性问题polyfill可以让我们处理浏览器兼容性问题,屏蔽浏览器差异。JavaScript库中经常使用外观模式来封装一些接口,以兼容多种浏览器,让我们可以间接调用我们封装好的外观,从而屏蔽浏览器差异,方便使用。例如兼容不同浏览器的常用事件绑定方法:functionaddEvent(element,type,fn){if(element.addEventListener){//支持DOM2级别事件处理方法的浏览器element.addEventListener(type,fn,false);}elseif(element.attachEvent){//不支持DOM2级别但支持attachEventelement.attachEvent('on'+type,fn);}else{element['on'+type]=fn;//不支持的浏览器}}varmyInput=document.getElementById('myinput');addEvent(myInput,'click',function(){console.log('Bindclickevent');})复制代码exceptevent除了绑定,我们还经常使用外观模式来解决浏览器兼容性的其他问题://RemoveeventsontheDOMfunctionremoveEvent(element,type,fn){if(element.removeEventListener){element.removeEventListener(类型,fn,假);}elseif(element.detachEvent){element.detachEvent('on'+type,fn);}else{element['on'+type]=null;}}//获取样式functiongetStyle(obj,styleName){if(window.getComputedStyle){varstyles=getComputedStyle(obj,null)[styleName];}else{varstyles=obj.currentStyle[styleName];返回圣yles;}//阻止默认事件varpreventDefault=function(event){if(event.preventDefault){event.preventDefault();}else{//IE下event.returnValue=false;}}//防止事件冒泡varcancelBubble=function(event){if(event.stopPropagation){event.stopPropagation();}else{//IE下event.cancelBubble=true;}}通过封装处理不同浏览器兼容性问题的过程来复制代码形成一个外观,我们在使用的时候可以直接使用外观方法。遇到兼容性问题时,这种外观方式自然会帮我们解决,方便又不容易出错。5.3Vue源码中的函数参数重载Vue提供了一个创建元素的方法,createElement(opensnewwindow),使用函数参数重载,使得用户在使用这个参数时非常灵活:exportfunctioncreateElement(context,tag,data,children,normalizationType,alwaysNormalize){if(Array.isArray(data)||isPrimitive(data)){//参数重载normalizationType=childrenchildren=datadata=undefined}//...}复制代码修改第三个参数data在createElement方法中判断,如果第三个参数的类型是array,string,number,boolean中的一种,则表示使用了createElement(tag[,data],children,...)。用户传递的第二个参数参数不是data,而是children。data该参数是一个包含模板相关属性的数据对象。如果用户没有什么可设置的,那么这个参数自然不会被传递。如果不使用函数参数重载,用户需要手动传递null或undefined。参数重载后,用户可以传或不传data参数,使用上有比较大的自由度,也很方便。createElement方法的源码见Github链接vue/src/core/vdom/create-element.js5.4重载了Lodash源码中的函数参数。Lodash的range方法的API是_.range([start=0],end,[step=1]),显然使用了参数重载,这个方法调用了一个内部函数createRange:functioncreateRange(fromRight){return(start,end,step)=>{//...if(end===undefined){end=startstart=0}//...}}复制代码表示如果不传第二个参数,然后将传入的第一个参数作为end,设置start为默认值。createRange方法的源码见Github链接lodash/.internal/createRange.js5.5jQuery源码中的函数参数重载函数参数重载在源码中用的比较多,在jQuery中也被广泛使用,比如on、off、bind、one、load、ajaxPrefilter等方法,这里以off方法为例,该方法为选中元素上的事件移除一个或多个事件处理器。源码如下:off:function(types,selector,fn){//...if(selector===false||typeofselector==='function'){//(types[,fn])usagefn=selectorselector=undefined}//...}复制代码可以看到如果传入的第二个参数为false或者函数,就是使用off(types[,fn])的方式。off方法的源码见Github链接jquery/src/event.jsload方法的源码:jQuery.fn.load=function(url,params,callback){//...if(isFunction(params)){callback=paramsparams=undefined}//...}复制代码可以看到jQuery对第二个参数进行了判断。如果是函数,就是使用load(url[,callback])的方式。load方法的源码见Github链接jquery/src/ajax/load.js(opensnewwindow)5.6jQuery源码中的外观模式当我们使用jQuery的$(document).ready(...)为浏览器加载事件添加回调时,jQuery将使用源代码中的bindReady方法:bindReady:function(){//...//Mozilla、Opera和webkit支持if(document.addEventListener){document...(document.attachEvent){文件。attachEvent('onreadystatechange',DOMContentLoaded)//回退到window.onload,它将始终有效window.attachEvent('onload',jQuery.ready)}//...}复制代码通过这个方法,jQuery将帮助我们隐藏了不同浏览器下的不同绑定形式,简化了使用。bindReady方法的源码见Github链接jquery/src/core.js除了屏蔽浏览器兼容性问题,jQuery还有其他外观模式的应用:比如修改css时,可以使用$('p').css('color','red'),或者$('p').css('width',100),将不同样式的操作封装到同一个外观方法中,极大的方便了使用,和不同样式的特殊处理(比如设置宽度时不加px)也封装在一起。源码可以参考Github链接jquery/src/css.js或者jQuery的ajaxAPI``$.ajax(url[,settings]),当我们设置请求以JSONP的形式发送时,我们只需要传入dataType:'jsonp'设置,jQuery会进行一些额外的操作来帮助我们启动JSONP流程,用户不需要手动添加代码,这些代码都封装在$.ajax()的外观方法中.源码见Github链接jquery/src/ajax/jsonp.js5.7Axios源码中的外观模式Axios可以在不同的环境下使用,所以在不同的环境下发送HTTP请求时,会使用特定的模块在不同的环境下,这里的Axios门面模式就是用来解决这个问题的:'[objectprocess]'){//在Nodejs中使用HTTP适配器adapter=require('./adapters/http');}elseif(typeofXMLHttpRequest!=='undefined'){//在浏览器中使用XHR适配器adapter=require('./adapters/xhr');}//...}该方法判断,如果在Nodejs环境下,使用NodejsHTTP模块发送请求,在浏览器环境下,使用浏览器APIXMLHTTPRequest。getDefaultAdapter方法源码见Github链接axios/lib/defaults.js5.8reduxcreateStorecreateStoreexportdefaultfunctioncreateStore(reducer,preloadedState,enhancer){if(typeofpreloadedState==='function'&&typeofenhancer==='undefined'){enhancer=preloadedStatepreloadedState=undefined}}复制代码6设计原则验证不符合单一职责原则和开闭原则,谨慎使用,不要滥用,只需要与外观交互,方便访问者使用子系统,符合最少知识原则,增强可移植性和可读性。减少了对子系统模块的直接引用,实现了子系统中访问者与模块的松耦合,增加了可维护性和可扩展性。合理使用外观模式可以帮助我们更好的划分系统的访问层次,比如将需要暴露在外的功能集中到外观中,既方便访问者使用,又能很好的隐藏内部细节,提高安全。性别。缺点:不符合开闭原则。它对修改关闭,对扩展开放。如果外观模块失效,只能通过修改解决问题,因为外观模块是子系统唯一的出口。不必要或不合理地使用外观可能会造成混淆,太多就是太多。7、外观模式的适用场景在维护设计粗糙、难以理解的遗留系统,或者系统非常复杂的时候,可以为这些系统设置外观模块,对外提供清晰的界面。未来,新系统只需要与外观进行交互。你写了几个小模块,可以完成某个大功能,但是以后会经常用到大功能,可以用外观来提供大功能,因为小的功能外界不需要了解模块。与团队合作时,您可以为您负责的模块创建合适的外观,以简化使用并节省沟通时间。如果构建多层系统,可以使用外观模式对系统进行分层,让外观模块成为每一层的入口,简化层间调用,松散层间耦合。8.其他相关模式8.1外观模式和中介模式外观模式:封装子用户与子系统中模块的直接交互,方便用户调用子系统。中介模式:封装子系统中模块之间的直接交互,模块之间松耦合。8.2外观模式和单例模式有时候一个系统只需要一种外观,比如前面提到的AxiosHTTP模块的例子。这时候我们就可以将外观模式和单例模式结合使用,将外观实现为单例。文章来自:peffy,如需转载本文,请联系今日热爱科学的Wesley头条号。github:https://github.com/zuopf769
