天天看點

設計模式第二彈: 不知道怎麼提高代碼複用性?看看這幾種設計模式吧!

本文是設計模式的第二篇文章,第一篇文章是不知道怎麼封裝代碼?看看這幾種設計模式吧!,後面還會有<code>提高擴充性</code>,<code>提高代碼品質</code>的設計模式,點個關注不迷路,哈哈~

想必大家都聽說過<code>DRY</code>原則,其實就是<code>Don't repeat yourself(不要重複你自己)</code>,意思就是不要重複寫一樣的代碼,換句話說就是要提高代碼的複用性。那什麼樣的代碼才算有好的複用性呢?

對象可以重複利用。這個其實有點像我們關系型資料庫的設計原則,資料表和關系表是分開的,資料表就是單純的資料,沒有跟其他表的關系,也沒有業務邏輯,關系表才是存儲具體的對應關系。當我們需要某個資料時,直接讀這個表就行,而不用擔心這個表會有其他的業務在裡面。類似設計的還有redux,redux的store裡面就是單純的資料,并不對應具體的業務邏輯,業務如果需要改變資料需要發action才行。正是因為這種資料很單純,是以我們需要的地方都可以拿來用,複用性非常高。是以我們設計資料或對象時,也要盡量讓他可以複用。 重複代碼少。如果你寫的代碼重複度很高的話,說明你代碼的抽象度不夠。很多時候我們重複代碼的産生都是因為我們可能需要寫一個跟已經存在的功能類似的功能,于是我們就把之前的代碼拷貝過來,把其中兩行代碼改了完事。這樣做雖然功能實作了,但是卻制造了大量重複代碼,本文要講的幾種設計模式就是用來解決這個問題的,提高代碼的抽象度,減少重複代碼。 子產品功能單一。這意味着一個子產品就專注于一個功能,我們需要做一個大功能時,就将多個子產品組合起來就行。這就像樂高積木,功能單一的子產品就像樂高積木的一小塊,我們可以用10個小塊拼成一個小汽車,也可以用20個小塊拼成一個大卡車。但是如果我們子產品本身做複雜了,做成了小汽車,我們是不能用兩個小汽車拼成一個大卡車的,這複用性就降低了。

提高複用性的設計模式主要有<code>橋接模式</code>,<code>享元模式</code>,<code>模闆方法模式</code>,下面我們分别來看下。

橋接模式人如其名,其實就相當于一個橋梁,把不同次元的變量橋接在一起來實作功能。假設我們需要實作三種形狀(長方形,圓形,三角形),每種形狀有三種顔色(紅色,綠色,藍色),這個需求有兩個方案,一個方案寫九個方法,每個方法實作一個圖形:

上述代碼雖然功能實作了,但是如果我們需求變了,我們要求再加一個顔色,那我們就得再加三個方法,每個形狀加一個。這麼多方法看着就很重複,意味着他有優化的空間。我們仔細看下這個需求,我們最終要畫的圖形有顔色和形狀兩個變量,這兩個變量其實是沒有強的邏輯關系的,完全是兩個次元的變量。那我們可以将這兩個變量拆開,最終要畫圖形的時候再橋接起來,就是這樣:

使用橋接模式後我們的方法從<code>3 * 3</code>變成了<code>3 + 1</code>,而且如果後續顔色增加了,我們隻需要稍微修改<code>showColor</code>方法,讓他支援新顔色就行了。如果我們變量的次元不是2,而是3,這種優勢會更加明顯,前一種需要的方法是<code>x * y * z</code>個,橋接模式優化後是<code>x + y + z</code>個,這直接就是指數級的優化。是以這裡橋接模式優化的核心思想是觀察重複代碼能不能拆成多個次元,如果可以的話就把不同次元拆出來,使用時再将這些次元橋接起來。

橋接模式其實我最喜歡的例子就是<code>毛筆和蠟筆</code>,因為這個例子非常直覺,好了解。這個例子的需求是要畫<code>細</code>,<code>中</code>,<code>粗</code>三種型号的線,每種型号的線需要5種顔色,如果我們用蠟筆來畫就需要15支蠟筆,如果我們換毛筆來畫,隻需要3支毛筆就行了,每次用不同顔色的墨水,用完換墨水就行。寫成代碼就是這樣,跟上面那個有點像:

上述例子中蠟筆因為大小和顔色都是他本身的屬性,沒法分開,需要的蠟筆數量是兩個次元的乘積,也就是15支,如果再多一個次元,那複雜度是指數級增長的。但是毛筆的大小和顔色這兩個次元是分開的,使用時将他們橋接在一起就行,隻需要三隻毛筆,5瓶墨水,複雜度大大降低了。上面代碼的顔色我建立了一個類,而上個例子畫圖形那裡的顔色是直接作為參數傳遞的,這樣做的目的是為了示範即使同一個設計模式也可以有不同的實作方案。具體采用哪種方案要根據我們實際的需求來,如果要橋接的隻是顔色這麼一個簡單變量,完全可以作為參數傳遞,如果要橋接一個複雜對象,可能就需要一個類了。另外上述代碼的三個筆的類看着就很重複,其實進一步優化還可以提取一個模闆,也就是筆的基類,具體可以看看後面的模闆方法模式。

這個例子的需求是:有多個菜單項,每個菜單項文字不一樣,滑鼠滑入滑出時文字的顔色也不一樣。我們一般實作時可能這麼寫代碼:

上述代碼看起來都好多重複的,為了消除這些重複代碼,我們将事件綁定和顔色設定這兩個次元分離開:

上述代碼也是一樣的思路,我們将事件綁定和顔色兩個次元分别抽取出來,使用的時候再橋接,進而減少了大量相似的代碼。

當我們觀察到代碼中有大量相似的代碼塊,他們做的事情可能都是一樣的,隻是每次應用的對象不一樣,我們就可以考慮用享元模式。現在假設我們有一個需求是顯示多個彈窗,每個彈窗的文字和大小不同:

如果我們不用享元模式,一個一個彈就是這樣:

我們仔細觀察上面的代碼,發現這兩個執行個體做的事情都是一樣的,都是顯示彈窗,但是每個彈窗的大小文字不一樣,那<code>show</code>方法是不是就可以提出來公用,把不一樣的部分作為參數傳進去就行。這種思路其實就是享元模式,我們改造如下:

我們再來看一個例子,假如我們現在有個需求是上傳檔案,可能需要上傳多個檔案,我們一般寫代碼可能就是這樣:

上述代碼我們需要上傳三個檔案于是執行個體化了三個<code>Uploader</code>,但其實這三個執行個體隻有檔案類型和檔案資料不一樣,其他的都是一樣的,我們可以重用一樣的部分,不一樣的部分作為參數傳進去就行了,用享元模式優化如下:

上述代碼我們通過參數的抽取将3個執行個體簡化為1個,提高了<code>Uploader</code>類的複用性。上述兩個例子其實是類似的,但他們隻是享元模式的一種形式,隻要是符合這種思想的都可以叫享元模式,比如jQuery裡面的<code>extend</code>方法也用到了享元模式。

jQuery的<code>extend</code>方法是大家經常用的一個方法了,他接收一個或者多個參數:

隻有一個參數時,<code>extend</code>會将傳入的參數合并到jQuery自己身上。 傳入兩個參數obj1和obj2時,<code>extend</code>會将obj2合并到obj1上。

根據上述需求,我們很容易自己實作:

上述代碼的<code>this[item] = arguments[0][item]</code>和<code>arguments[0][item] = arguments[1][item]</code>看着就很像,我們想想能不能優化下他,仔細看着兩行代碼,他們不同的地方是拷貝的目标和來源不一樣,但是拷貝的操作卻是一樣的。是以我們用享元模式優化下,将不同的地方抽出來,保持共用的拷貝不變:

模闆方法模式其實類似于繼承,就是我們先定義一個通用的模闆骨架,然後後面在這個基礎上繼續擴充。我們通過一個需求來看下他的基本結構,假設我們現在需要實作一個導航元件,但是這個導航類型還比較多,有的帶消息提示,有的是橫着的,有的是豎着的,而且後面還可能會新增類型:

上述代碼我們先建了一個基礎的類,裡面隻有最基本的屬性和方法,其實就相當于一個模闆,而且在具體的方法裡面還可以接收回調,這樣後面派生出來的類可以根據自己的需求傳入回調。模闆方法模式其實就是類似于面向對象的基類和派生類的關系,下面我們再來看一個例子。

還是之前用過的彈窗例子,我們要做一個大小文字可能不同的彈窗元件,隻是這次我們的彈窗還有取消和确定兩個按鈕,這兩個按鈕在不同場景下可能有不同的行為,比如發起請求什麼的。但是他們也有一個共同的操作,就是點選這兩個按鈕後彈窗都會消失,這樣我們就可以把共同的部分先寫出來,作為一個模闆:

現在我們有了一個基礎的模闆,那假如我們還需要在點選取消或者确認後再進行其他操作,比如發起請求,我們可以以這個模闆為基礎再加上後面需要的操作就行:

上面這個例子是通過繼承實作了模闆方法模式,但是這個模式并不是一定要用繼承的,他強調的是将一些基礎部分提取出來作為模闆,後面更多的操作可以在這個基礎上進行擴充。

這個例子我們就不用繼承了,他的需求是我們現在有一系列的算法,但是這些算法在具體用的時候可能還會添加一些不同的計算操作,需要添加的操作可能在這個算法前執行,也可能在這個算法後執行。

根據需求我們要解決的問題是在基本算法計算時可能還有其他計算操作,這些操作可能在基本計算前,也可能在基本計算之後,是以我們要在這個計算類上留出可擴充的接口:

這次我們沒有用繼承了,但是我們仍然是先定義了一個基本的操作骨架,然後在這個骨架上去擴充不同地方需要的特殊操作。

如果我們的代碼中出現了大量相似的代碼塊,往往意味着有進一步的優化空間。

如果這些重複代碼塊可以拆分成不同的次元,那可以試試橋接模式,先将次元拆開,再橋接這些次元來使用。

如果這些重複代碼有一部分操作是一樣的,但是每次操作的對象不一樣,我們可以考慮用享元模式将公有操作提取成方法,将私有部分作為參數傳進去。

如果這些重複代碼有一些基本操作是一樣的,但是具體應用時需要的功能更多,我們可以考慮将這些基本操作提取成模闆,然後在模闆上留出擴充接口,需要的地方可以通過這些接口來擴充功能,有點類似于繼承,但實作方式并不僅限于繼承。

我們将重複部分提取出來,其他地方也可以用,其實就是提高了代碼的複用性。

還是那句話,設計模式沒有固定的範式,主要還是要了解他的思想,代碼在不同地方可以有不同的實作方式。

文章的最後,感謝你花費寶貴的時間閱讀本文,如果本文給了你一點點幫助或者啟發,請不要吝啬你的贊和GitHub小星星,你的支援是作者持續創作的動力。

本文素材來自于網易進階前端開發工程師微專業唐磊老師的設計模式課程。

歡迎關注我的公衆号進擊的大前端第一時間擷取高品質原創~

“前端進階知識”系列文章:https://juejin.im/post/5e3ffc85518825494e2772fd

“前端進階知識”系列文章源碼GitHub位址: https://github.com/dennis-jiang/Front-End-Knowledges

設計模式第二彈: 不知道怎麼提高代碼複用性?看看這幾種設計模式吧!

繼續閱讀