1. 定義
外觀模式( )又叫門面模式,指提供一個統一的接口去訪問多個子系統的多個不同的接口,為子系統中的一組接口提供統一的高層接口 。使得子系統更容易使用,不僅簡化類中的接口 , 而且實現調用者和接口的解耦 。
2. 類圖
該設計模式由以下角色組成
門面角色:
外觀模式的核心 。它被客戶角色調用,它熟悉子系統的功能 。內部根據客戶角色的需求預定了幾種功能的組合 子系統角色:實現了子系統的功能 。它對客戶角色和是未知的 。
子系統角色:
實現了子系統的功能 。它對客戶角色和是未知的
客戶角色
通過調用來完成要實現的功能
門面模式類圖
3. 一個生活中的例子
比如常見的空調、冰箱、洗衣機,內部結構都并不簡單,對于我們使用者而言 , 理解他們內部的運行機制的門檻比較高,但是理解遙控器/控制面板上面寥寥幾個按鈕就相對容易的多 , 這就是外觀模式的意義 。
遙控器
在類似場景中,這些例子有以下特點:
一個統一的外觀為復雜的子系統提供一個簡單的高層功能接口 。原本訪問者直接調用子系統內部模塊導致的復雜引用關系,現在可以通過只訪問這個統一的外觀來避免 。
4. 通用實現
在外觀模式中,客戶端直接對接外觀(),通過接口去對接子接口,而子接口里封裝的一系列復雜操作 , 則不是我們要的重點 。
結構如下:
外觀模式一般是作為子系統的功能出口出現 , 使用的時候可以在其中增加新的功能,但是不推薦這樣做,因為外觀應該是對已有功能的包裝 , 不應在其中摻雜新的功能 。
4.1 計算器
class Sum {sum(a, b) {return a + b;}}class Minus {minus(a, b) {return a - b;}}class Multiply {multiply(a, b) {return a * b;}}class Calculator {sumObjminusObjmultiplyObjconstructor() {this.sumObj = new Sum();this.minusObj = new Minus();this.multiplyObj = new Multiply();}sum(...args) {return this.sumObj.sum(...args);}minus(...args) {return this.minusObj.minus(...args);}multiply(...args) {return this.multiplyObj.multiply(...args);}}let calculator = new Calculator();console.log(calculator.sum(1, 2));console.log(calculator.minus(1, 2));console.log(calculator.multiply(1, 2));復制代碼
4.2 計算機
class CPU {startup() { console.log('打開CPU'); }shutdown() { console.log('關閉CPU'); }}class Memory {startup() { console.log('打開內存'); }shutdown() { console.log('關閉內存'); }}class Disk {startup() { console.log('打開硬盤'); }shutdown() { console.log('關閉硬盤'); }}class Computer {cpu;memory;disk;constructor() {this.cpu = new CPU();this.memory = new Memory();this.disk = new Disk();}startup() {this.cpu.startup();this.memory.startup();this.disk.startup();}shutdown() {this.cpu.shutdown();this.memory.shutdown();this.disk.shutdown();}}let computer = new Computer();computer.startup();computer.shutdown();復制代碼
4.3 壓縮
export { }var zlib = require('zlib');var fs = require('fs');let path = require('path');function open(input) {let ext = path.extname(input);switch (ext) {case '.gz':return unZip(input);case '.rar':return unRar(input);case '.7z':return un7z(input);default:break;}}function unZip(src) {var gunzip = zlib.createGunzip();var inputStream = fs.createReadStream(src);var outputStream = fs.createWriteStream(src.slice(0, -3));console.log('outputStream');inputStream.pipe(gunzip).pipe(outputStream);}function unRar(src) {console.log('Rar解壓后的', src);}function un7z(src) {console.log('7z解壓后的', src);}open('./source.txt.gz');function zip(src) {var gzip = zlib.createGzip();//創建壓縮流var inputStream = fs.createReadStream(src);var outputStream = fs.createWriteStream(src+'.gz');inputStream.pipe(gzip).pipe(outputStream);}zip('source.txt');復制代碼
5. 前端應用場景5.1 函數參數重載
有一種情況css如何做一個提交按鈕,比如某個函數有多個參數 , 其中一個參數可以傳遞也可以不傳遞,你當然可以直接弄兩個接口,但是使用函數參數重載的方式,可以讓使用者獲得更大的自由度 , 讓兩個使用上基本類似的方法獲得統一的外觀 。
function bindEvent(elem, type, selector, fn) {if (fn === undefined) {fn = selector;selector = null;}// ... 剩下相關邏輯}bindEvent(elem, 'click', '#div1', fn)bindEvent(elem, 'click', fn)復制代碼
上面這個綁定事件的函數中 , 參數就是可選的 。
這種方式在一些工具庫或者框架提供的多功能方法上經常得到使用,特別是在通用 API 的某些參數可傳可不傳的時候 。
參數重載之后的函數在使用上會獲得更大的自由度,而不必重新創建一個新的 API,這在 Vue、React、、 等庫中使用非常頻繁 。
5.2 抹平瀏覽器兼容性問題
可以讓我們處理瀏覽器兼容和屏蔽了瀏覽器差異
外觀模式經常被用于的庫中 , 封裝一些接口用于兼容多瀏覽器,讓我們可以間接調用我們封裝的外觀,從而屏蔽了瀏覽器差異,便于使用 。
比如經常用的兼容不同瀏覽器的事件綁定方法:
function addEvent(element, type, fn) {if (element.addEventListener) {// 支持 DOM2 級事件處理方法的瀏覽器element.addEventListener(type, fn, false);} else if (element.attachEvent) {// 不支持 DOM2 級但支持 attachEventelement.attachEvent('on' + type, fn);} else {element['on' + type] = fn;// 都不支持的瀏覽器}}var myInput = document.getElementById('myinput');addEvent(myInput, 'click', function() {console.log('綁定 click 事件');})復制代碼
除了事件綁定之外,在抹平瀏覽器兼容性的其他問題上我們也經常使用外觀模式:

文章插圖

文章插圖
// 移除 DOM 上的事件function removeEvent(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;}}// 獲取樣式function getStyle(obj, styleName) {if (window.getComputedStyle) {var styles = getComputedStyle(obj, null)[styleName];} else {var styles = obj.currentStyle[styleName];}return styles;}// 阻止默認事件var preventDefault = function(event) {if (event.preventDefault) {event.preventDefault();} else {// IE 下event.returnValue = https://www.jianzixun.com/false;}}// 阻止事件冒泡var cancelBubble = function(event) {if (event.stopPropagation) {event.stopPropagation();} else {// IE 下event.cancelBubble = true;}}復制代碼通過將處理不同瀏覽器兼容性問題的過程封裝成一個外觀,我們在使用的時候可以直接使用外觀方法即可,在遇到兼容性問題的時候,這個外觀方法自然幫我們解決,方便又不容易出錯 。
5.3 Vue 源碼中的函數參數重載
Vue 提供的一個創建元素的方法(opens new )就使用了函數參數重載,使得使用者在使用這個參數的時候很靈活:
export function createElement(context,tag,data,children,normalizationType,alwaysNormalize) {if (Array.isArray(data) || isPrimitive(data)) {// 參數的重載normalizationType = childrenchildren = datadata = https://www.jianzixun.com/undefined}// ...}復制代碼方法里面對第三個參數 data 進行了判斷,如果第三個參數的類型是 array、、、 中的一種,那么說明是 (tag [, data], , …) 這樣的使用方式,用戶傳的第二個參數不是 data,而是。
data 這個參數是包含模板相關屬性的數據對象 , 如果用戶沒有什么要設置,那這個參數自然不傳,不使用函數參數重載的情況下,需要用戶手動傳遞 null 或者之類,參數重載之后,用戶對 data 這個參數可傳可不傳,使用自由度比較大,也很方便 。
方法的源碼參見鏈接vue/src/core/vdom/-.js
5.4源碼中的函數參數重載
的 range 方法的 API 為 _.range([start=0], end, [step=1]),這就很明顯使用了參數重載,這個方法調用了一個內部函數 :
function createRange(fromRight) {return (start, end, step) => {// ...if (end === undefined) {end = startstart = 0}// ...}}復制代碼意思就是,如果沒有傳第二個參數 , 那么就把傳入的第一個參數作為 end , 并把 start 置為默認值 。
方法的源碼參見鏈接 /./.js
【前端必懂的設計模式-門面模式】5.5源碼中的函數參數重載
函數參數重載在源碼中使用比較多,中也有大量使用css如何做一個提交按鈕,比如 on、off、bind、one、load、 等方法,這里以 off 方法為例 , 該方法在選擇元素上移除一個或多個事件的事件處理函數 。源碼如下:
off: function (types, selector, fn) { // ...if (selector === false || typeof selector === 'function') {// ( types [, fn] ) 的使用方式fn = selectorselector = undefined }// ...}復制代碼可以看到如果傳入第二個參數為 false 或者是函數的時候,就是 off(types [, fn]) 的使用方式 。
off 方法的源碼參見鏈接 /src/event.js
再比如 load 方法的源碼:
jQuery.fn.load = function(url, params, callback) {// ...if (isFunction(params)) {callback = paramsparams = undefined}// ...}復制代碼可以看到對第二個參數進行了判斷,如果是函數,就是 load(url [, ]) 的使用方式 。
load 方法的源碼參見鏈接 /src/ajax/load.js(opens new )
5.6源碼中的外觀模式
當我們使用的 $().ready(…) 來給瀏覽器加載事件添加回調時 , 會使用源碼中的方法:
bindReady: function() {// ...// Mozilla, Opera and webkit 支持if (document.addEventListener) {document.addEventListener('DOMContentLoaded', DOMContentLoaded, false)// A fallback to window.onload, that will always workwindow.addEventListener('load', jQuery.ready, false)// 如果使用了 IE 的事件綁定形式} else if (document.attachEvent) {document.attachEvent('onreadystatechange', DOMContentLoaded)// A fallback to window.onload, that will always workwindow.attachEvent('onload', jQuery.ready)}// ...}復制代碼通過這個方法,幫我們將不同瀏覽器下的不同綁定形式隱藏起來,從而簡化了使用 。
方法的源碼參見鏈接 /src/core.js
除了屏蔽瀏覽器兼容性問題之外,還有其他的一些其他外觀模式的應用:
比如修改 css 的時候可以 $('p').css('color', 'red') , 也可以 $('p').css('width', 100),對不同樣式的操作被封裝到同一個外觀方法中,極大地方便了使用,對不同樣式的特殊處理(比如設置 width 的時候不用加 px)也一同被封裝了起來 。
源碼參見鏈接 /src/css.js
再比如的 ajax 的 API “$.ajax(url [, ]),當我們在設置以 JSONP 的形式發送請求的時候,只要傳入 : 'jsonp' 設置,會進行一些額外操作幫我們啟動 JSONP 流程,并不需要使用者手動添加代碼,這些都被封裝在 $.ajax() 這個外觀方法中了 。
源碼參見鏈接 /src/ajax/jsonp.js
5.7 Axios 源碼中的外觀模式
Axios 可以使用在不同環境中,那么在不同環境中發送 HTTP 請求的時候會使用不同環境中的特有模塊,Axios 這里是使用外觀模式來解決這個問題的:
function getDefaultAdapter() {// ...if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {// Nodejs 中使用 HTTP adapteradapter = require('./adapters/http');} else if (typeof XMLHttpRequest !== 'undefined') {// 瀏覽器使用 XHR adapteradapter = require('./adapters/xhr');}// ...}復制代碼這個方法進行了一個判斷,如果在的環境中則使用的 HTTP 模塊來發送請求,在瀏覽器環境中則使用這個瀏覽器 API 。
方法源碼參見鏈接 axios/lib/.js
5.8 redux
export default function createStore(reducer, preloadedState, enhancer) {if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {enhancer = preloadedStatepreloadedState = undefined}}復制代碼6 設計原則驗證
不符合單一職責原則和開放封閉原則 , 因此謹慎使用 , 不可濫用
7 外觀模式的優缺點優點:缺點:7. 外觀模式的適用場景8. 其他相關模式8.1 外觀模式與中介者模式8.2 外觀模式與單例模式
有時候一個系統只需要一個外觀,比如之前舉的 Axios 的 HTTP 模塊例子 。這時我們可以將外觀模式和單例模式一起使用,把外觀實現為單例 。
本文到此結束,希望對大家有所幫助 。
- 春節必備,原創寶典 3分鐘學會看懂對象的原生家庭
- 興城旅游攻略必玩的景點 興城旅游攻略
- 孕期有必要拍孕婦照嗎 ?孕婦有必要拍孕照嗎
- 家長必看!這些繪本教會孩子如何克服恐懼心理
- 北京購物攻略必去的地方 北京購物攻略
- 南京海底世界旅游攻略景點必去 南京海底世界旅游攻略
- 南戴河旅游攻略必玩的景點 南戴河旅游攻略
- 前端為什么棄用jQuery?有這六個原因
- 輕諾必寡信含義講解和使用場合 ?輕諾必寡信什么意思
- 十二星座的女神都喜歡什么花?送老婆、送女友、送女神必備攻略
