您好,登錄后才能下訂單哦!
這篇文章主要講解了“提高代碼復用性的web設計模式有哪些”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“提高代碼復用性的web設計模式有哪些”吧!
橋接模式
橋接模式人如其名,其實就相當于一個橋梁,把不同維度的變量橋接在一起來實現(xiàn)功能。假設我們需要實現(xiàn)三種形狀(長方形,圓形,三角形),每種形狀有三種顏色(紅色,綠色,藍色),這個需求有兩個方案,一個方案寫九個方法,每個方法實現(xiàn)一個圖形:
function redRectangle() {} function greenRectangle() {} function blueRectangle() {} function redCircle() {} function greenCircle() {} function blueCircle() {} function redTriangle() {} function greenTriangle() {} function blueTriangle() {}
上述代碼雖然功能實現(xiàn)了,但是如果我們需求變了,我們要求再加一個顏色,那我們就得再加三個方法,每個形狀加一個。這么多方法看著就很重復,意味著他有優(yōu)化的空間。我們仔細看下這個需求,我們最終要畫的圖形有顏色和形狀兩個變量,這兩個變量其實是沒有強的邏輯關(guān)系的,完全是兩個維度的變量。那我們可以將這兩個變量拆開,最終要畫圖形的時候再橋接起來,就是這樣:
function rectangle(color) { // 長方形 showColor(color); } function circle(color) { // 圓形 showColor(color); } function triangle(color) { // 三角形 showColor(color); } function showColor(color) { // 顯示顏色的方法 } // 使用時,需要一個紅色的圓形 let obj = new circle('red');
使用橋接模式后我們的方法從3 * 3變成了3 + 1,而且如果后續(xù)顏色增加了,我們只需要稍微修改showColor方法,讓他支持新顏色就行了。如果我們變量的維度不是2,而是3,這種優(yōu)勢會更加明顯,前一種需要的方法是x * y * z個,橋接模式優(yōu)化后是x + y + z個,這直接就是指數(shù)級的優(yōu)化。所以這里橋接模式優(yōu)化的核心思想是觀察重復代碼能不能拆成多個維度,如果可以的話就把不同維度拆出來,使用時再將這些維度橋接起來。
實例:毛筆和蠟筆
橋接模式其實我最喜歡的例子就是毛筆和蠟筆,因為這個例子非常直觀,好理解。這個例子的需求是要畫細,中,粗三種型號的線,每種型號的線需要5種顏色,如果我們用蠟筆來畫就需要15支蠟筆,如果我們換毛筆來畫,只需要3支毛筆就行了,每次用不同顏色的墨水,用完換墨水就行。寫成代碼就是這樣,跟上面那個有點像:
// 先來三個筆的類 function smallPen(color) { this.color = color; } smallPen.prototype.draw = function() { drawWithColor(this.color); // 用color顏色來畫畫 } function middlePen(color) { this.color = color; } middlePen.prototype.draw = function() { drawWithColor(this.color); // 用color顏色來畫畫 } function bigPen(color) { this.color = color; } bigPen.prototype.draw = function() { drawWithColor(this.color); // 用color顏色來畫畫 } // 再來一個顏色類 function color(color) { this.color = color; } // 使用時 new middlePen(new color('red')).draw(); // 畫一個中號的紅線 new bigPen(new color('green')).draw(); // 畫一個大號的綠線
上述例子中蠟筆因為大小和顏色都是他本身的屬性,沒法分開,需要的蠟筆數(shù)量是兩個維度的乘積,也就是15支,如果再多一個維度,那復雜度是指數(shù)級增長的。但是毛筆的大小和顏色這兩個維度是分開的,使用時將他們橋接在一起就行,只需要三只毛筆,5瓶墨水,復雜度大大降低了。上面代碼的顏色我新建了一個類,而上個例子畫圖形那里的顏色是直接作為參數(shù)傳遞的,這樣做的目的是為了演示即使同一個設計模式也可以有不同的實現(xiàn)方案。具體采用哪種方案要根據(jù)我們實際的需求來,如果要橋接的只是顏色這么一個簡單變量,完全可以作為參數(shù)傳遞,如果要橋接一個復雜對象,可能就需要一個類了。另外上述代碼的三個筆的類看著就很重復,其實進一步優(yōu)化還可以提取一個模板,也就是筆的基類,具體可以看看后面的模板方法模式。
實例:菜單項
這個例子的需求是:有多個菜單項,每個菜單項文字不一樣,鼠標滑入滑出時文字的顏色也不一樣。我們一般實現(xiàn)時可能這么寫代碼:
function menuItem(word) { this.dom = document.createElement('div'); this.dom.innerHTML = word; } var menu1 = new menuItem('menu1'); var menu2 = new menuItem('menu2'); var menu3 = new menuItem('menu3'); // 給每個menu設置鼠標滑入滑出事件 menu1.dom.onmouseover = function(){ menu1.dom.style.color = 'red'; } menu2.dom.onmouseover = function(){ menu1.dom.style1.color = 'green'; } menu3.dom.onmouseover = function(){ menu1.dom.style1.color = 'blue'; } menu1.dom.onmouseout = function(){ menu1.dom.style1.color = 'green'; } menu2.dom.onmouseout = function(){ menu1.dom.style1.color = 'blue'; } menu3.dom.onmouseout = function(){ menu1.dom.style1.color = 'red'; }
上述代碼看起來都好多重復的,為了消除這些重復代碼,我們將事件綁定和顏色設置這兩個維度分離開:
// 菜單項類多接收一個參數(shù)color function menuItem(word, color) { this.dom = document.createElement('div'); this.dom.innerHTML = word; this.color = color; // 將接收的顏色參數(shù)作為實例屬性 } // 菜單項類添加一個實例方法,用于綁定事件 menuItem.prototype.bind = function() { var that = this; // 這里的this指向menuItem實例對象 this.dom.onmouseover = function() { this.style.color = that.color.colorOver; // 注意這里的this是事件回調(diào)里面的this,指向DOM節(jié)點 } this.dom.onmouseout = function() { this.style.color = that.color.colorOut; } } // 再建一個類存放顏色,目前這個類的比較簡單,后面可以根據(jù)需要擴展 function menuColor(colorOver, colorOut) { this.colorOver = colorOver; this.colorOut = colorOut; } // 現(xiàn)在新建菜單項可以直接用一個數(shù)組來循環(huán)了 var menus = [ {word: 'menu1', colorOver: 'red', colorOut: 'green'}, {word: 'menu2', colorOver: 'green', colorOut: 'blue'}, {word: 'menu3', colorOver: 'blue', colorOut: 'red'}, ] for(var i = 0; i < menus.length; i++) { // 將參數(shù)傳進去進行實例化,最后調(diào)一下bind方法,這樣就會自動綁定事件了 new menuItem(menus[i].word, new menuColor(menus[i].colorOver, menus[i].colorOut)).bind(); }
上述代碼也是一樣的思路,我們將事件綁定和顏色兩個維度分別抽取出來,使用的時候再橋接,從而減少了大量相似的代碼。
享元模式
當我們觀察到代碼中有大量相似的代碼塊,他們做的事情可能都是一樣的,只是每次應用的對象不一樣,我們就可以考慮用享元模式?,F(xiàn)在假設我們有一個需求是顯示多個彈窗,每個彈窗的文字和大小不同:
// 已經(jīng)有一個彈窗類了 function Popup() {} // 彈窗類有一個顯示的方法 Popup.prototype.show = function() {}
如果我們不用享元模式,一個一個彈就是這樣:
var popup1 = new Popup(); popup1.show(); var popup2 = new Popup(); popup2.show();
我們仔細觀察上面的代碼,發(fā)現(xiàn)這兩個實例做的事情都是一樣的,都是顯示彈窗,但是每個彈窗的大小文字不一樣,那show方法是不是就可以提出來公用,把不一樣的部分作為參數(shù)傳進去就行。這種思路其實就是享元模式,我們改造如下:
var popupArr = [ {text: 'popup 1', width: 200, height: 400}, {text: 'popup 2', width: 300, height: 300}, ] var popup = new Popup(); for(var i = 0; i < popupArr.length; i++) { popup.show(popupArr[i]); // 注意show方法需要接收參數(shù) }
實例:文件上傳
我們再來看一個例子,假如我們現(xiàn)在有個需求是上傳文件,可能需要上傳多個文件,我們一般寫代碼可能就是這樣:
// 一個上傳的類 function Uploader(fileType, file) { this.fileType = fileType; this.file = file; } Uploader.prototype.init = function() {} // 初始化方法 Uploader.prototype.upload = function() {} // 具體上傳的方法 var file1, file2, file3; // 多個需要上傳的文件 // 每個文件都實例化一個Uploader new Uploader('img', file1).upload(); new Uploader('txt', file2).upload(); new Uploader('mp3', file3).upload();
上述代碼我們需要上傳三個文件于是實例化了三個Uploader,但其實這三個實例只有文件類型和文件數(shù)據(jù)不一樣,其他的都是一樣的,我們可以重用一樣的部分,不一樣的部分作為參數(shù)傳進去就行了,用享元模式優(yōu)化如下:
// 文件數(shù)據(jù)扔到一個數(shù)組里面 var data = [ {filetype: 'img', file: file1}, {filetype: 'txt', file: file2}, {filetype: 'mp3', file: file3}, ]; // Uploader類改造一下, 構(gòu)造函數(shù)不再接收參數(shù) function Uploader() {} // 原型上的其他方法保持不變 Uploader.prototype.init = function() {} // 文件類型和文件數(shù)據(jù)其實是上傳的時候才用,作為upload的參數(shù) Uploader.prototype.upload = function(fileType, file) {} // 調(diào)用時只需要一個實例,循環(huán)調(diào)用upload就行 var uploader = new Uploader(); for(var i = 0; i < data.length; i++) { uploader.upload(data[i].filetype, data[i].file) }
上述代碼我們通過參數(shù)的抽取將3個實例簡化為1個,提高了Uploader類的復用性。上述兩個例子其實是類似的,但他們只是享元模式的一種形式,只要是符合這種思想的都可以叫享元模式,比如jQuery里面的extend方法也用到了享元模式。
實例:jQuery的extend方法
jQuery的extend方法是大家經(jīng)常用的一個方法了,他接收一個或者多個參數(shù):
只有一個參數(shù)時,extend會將傳入的參數(shù)合并到jQuery自己身上。
傳入兩個參數(shù)obj1和obj2時,extend會將obj2合并到obj1上。
根據(jù)上述需求,我們很容易自己實現(xiàn):
$.extend = function() { if(arguments.length === 1) { for(var item in arguments[0]) { this[item] = arguments[0][item] } } else if(arguments.length === 2) { for(var item in arguments[1]) { arguments[0][item] = arguments[1][item]; } } }
上述代碼的this[item] = arguments[0][item]和arguments[0][item] = arguments[1][item]看著就很像,我們想想能不能優(yōu)化下他,仔細看著兩行代碼,他們不同的地方是拷貝的目標和來源不一樣,但是拷貝的操作卻是一樣的。所以我們用享元模式優(yōu)化下,將不同的地方抽出來,保持共用的拷貝不變:
$.extend = function() { // 不同的部分抽取出兩個變量 var target = this; // 默認為this,即$本身 var source = arguments[0]; // 默認為第一個變量 // 如果有兩個參數(shù), 改變target和source if(arguments.length === 2) { target = arguments[0]; source = arguments[1]; } // 共同的拷貝操作保持不變 for(var item in source) { target[item] = source[item]; } }
模板方法模式
模板方法模式其實類似于繼承,就是我們先定義一個通用的模板骨架,然后后面在這個基礎上繼續(xù)擴展。我們通過一個需求來看下他的基本結(jié)構(gòu),假設我們現(xiàn)在需要實現(xiàn)一個導航組件,但是這個導航類型還比較多,有的帶消息提示,有的是橫著的,有的是豎著的,而且后面還可能會新增類型:
// 先建一個基礎的類 function baseNav() { } baseNav.prototype.action = function(callback){} //接收一個回調(diào)進行特異性處理
上述代碼我們先建了一個基礎的類,里面只有最基本的屬性和方法,其實就相當于一個模板,而且在具體的方法里面還可以接收回調(diào),這樣后面派生出來的類可以根據(jù)自己的需求傳入回調(diào)。模板方法模式其實就是類似于面向?qū)ο蟮幕惡团缮惖年P(guān)系,下面我們再來看一個例子。
實例:彈窗
還是之前用過的彈窗例子,我們要做一個大小文字可能不同的彈窗組件,只是這次我們的彈窗還有取消和確定兩個按鈕,這兩個按鈕在不同場景下可能有不同的行為,比如發(fā)起請求什么的。但是他們也有一個共同的操作,就是點擊這兩個按鈕后彈窗都會消失,這樣我們就可以把共同的部分先寫出來,作為一個模板:
function basePopup(word, size) { this.word = word; this.size = size; this.dom = null; } basePopup.prototype.init = function() { // 初始化DOM元素 var div = document.createElement('div'); div.innerHTML = this.word; div.style.width = this.size.width; div.style.height = this.size.height; this.dom = div; } // 取消的方法 basePopup.prototype.cancel = function() { this.dom.style.display = 'none'; } // 確認的方法 basePopup.prototype.confirm = function() { this.dom.style.display = 'none'; }
現(xiàn)在我們有了一個基礎的模板,那假如我們還需要在點擊取消或者確認后再進行其他操作,比如發(fā)起請求,我們可以以這個模板為基礎再加上后面需要的操作就行:
// 先繼承basePopup function ajaxPopup(word, size) { basePopup.call(this, word, size); } ajaxPopup.prototype = new basePopup(); ajaxPopup.prototype.constructor = ajaxPopup; // 上面是一個繼承的標準寫法,其實就相當于套用了模板 // 下面來加上需要的發(fā)起網(wǎng)絡請求的操作 var cancel = ajaxPopup.prototype.cancel; // 先緩存模板上的cancel方法 ajaxPopup.prototype.cancel = function() { // 先調(diào)模板的cancel cancel.call(this); // 再加上特殊的處理,比如發(fā)起請求 $.ajax(); } // confirm方法是一樣的處理 var confirm = ajaxPopup.prototype.confirm; ajaxPopup.prototype.confirm = function() { confirm.call(this); $.ajax(); }
上面這個例子是通過繼承實現(xiàn)了模板方法模式,但是這個模式并不是一定要用繼承的,他強調(diào)的是將一些基礎部分提取出來作為模板,后面更多的操作可以在這個基礎上進行擴展。
實例:算法計算器
這個例子我們就不用繼承了,他的需求是我們現(xiàn)在有一系列的算法,但是這些算法在具體用的時候可能還會添加一些不同的計算操作,需要添加的操作可能在這個算法前執(zhí)行,也可能在這個算法后執(zhí)行。
// 先定義一個基本的類 function counter() { } // 類上有一個計算方法 counter.prototype.count = function(num) { // 里面有一個算法本身的基本計算方法 function baseCount(num) { // 這里的算法是什么不重要,我們這里就加1吧 num += 1; return num; } }
根據(jù)需求我們要解決的問題是在基本算法計算時可能還有其他計算操作,這些操作可能在基本計算前,也可能在基本計算之后,所以我們要在這個計算類上留出可擴展的接口:
function counter() { // 添加兩個隊列,用于基本算法前或者后執(zhí)行 this.beforeCounting = []; this.afterCounting = []; } // 添加一個接口,接收基本算法計算前應該進行的計算 counter.prototype.before = function(fn) { this.beforeCounting.push(fn); // 直接將方法放進數(shù)組里面 } // 再添加一個接口,接收基本算法計算后應該進行的計算 counter.prototype.after = function(fn) { this.afterCounting.push(fn); } // 改造計算方法,讓他按照計算前-基本計算-計算后執(zhí)行 counter.prototype.count = function(num) { function baseCount(num) { num += 1; return num; } var result = num; var arr = [baseCount]; // 將需要進行的計算都放到這個數(shù)組里面 arr = this.beforeCounting.concat(arr); // 計算前操作放到數(shù)組前面 arr = arr.concat(this.afterCounting); // 計算后操作放到數(shù)組后面 // 將數(shù)組全部按順序拿出來執(zhí)行 while(arr.length > 0) { result = arr.shift()(result); } return result; } // 現(xiàn)在counter就可以直接使用了 var counterIntance = new counter(); counterIntance.before(num => num + 10); // 計算前先加10 counterIntance.after(num => num - 5); // 計算后再減5 counterIntance.count(2); // 2 + 10 + 1 - 5 = 8
這次我們沒有用繼承了,但是我們?nèi)匀皇窍榷x了一個基本的操作骨架,然后在這個骨架上去擴展不同地方需要的特殊操作。
感謝各位的閱讀,以上就是“提高代碼復用性的web設計模式有哪些”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對提高代碼復用性的web設計模式有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。