- JavaScript設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐
- 曾探
- 5118字
- 2020-01-10 15:38:11
前言
《設(shè)計(jì)模式》一書(shū)自1995年成書(shū)一來(lái),一直是程序員談?wù)摰摹案叨恕痹掝}之一。許多程序員從設(shè)計(jì)模式中學(xué)到了設(shè)計(jì)軟件的靈感,或者找到了問(wèn)題的解決方案。在社區(qū)中,既有人對(duì)模式無(wú)比崇拜,也有人對(duì)模式充滿誤解。有些程序員把設(shè)計(jì)模式視為圣經(jīng),唯模式至上;有些人卻認(rèn)為設(shè)計(jì)模式只在C++或者Java中有用武之地,JavaScript這種動(dòng)態(tài)語(yǔ)言根本就沒(méi)有設(shè)計(jì)模式一說(shuō)。
那么,在進(jìn)入設(shè)計(jì)模式的學(xué)習(xí)之前,我們最好還是從模式的起源說(shuō)起,分別聽(tīng)聽(tīng)這些不同的聲音。
設(shè)計(jì)模式并非是軟件開(kāi)發(fā)的專業(yè)術(shù)語(yǔ)。實(shí)際上,“模式”最早誕生于建筑學(xué)。20世紀(jì)70年代,哈佛大學(xué)建筑學(xué)博士Christopher Alexander和他的研究團(tuán)隊(duì)花了約20年的時(shí)間,研究了為解決同一個(gè)問(wèn)題而設(shè)計(jì)出的不同建筑結(jié)構(gòu),從中發(fā)現(xiàn)了那些高質(zhì)量設(shè)計(jì)中的相似性,并且用“模式”來(lái)指代這種相似性。
受Christopher Alexander工作的啟發(fā),Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides四人(人稱Gang Of Four , GoF)把這種“模式”觀點(diǎn)應(yīng)用于面向?qū)ο蟮能浖O(shè)計(jì)中,并且總結(jié)了23種常見(jiàn)的軟件開(kāi)發(fā)設(shè)計(jì)模式,錄入《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》一書(shū)。
設(shè)計(jì)模式的定義是:在面向?qū)ο筌浖O(shè)計(jì)過(guò)程中針對(duì)特定問(wèn)題的簡(jiǎn)潔而優(yōu)雅的解決方案。
通俗一點(diǎn)說(shuō),設(shè)計(jì)模式是在某種場(chǎng)合下對(duì)某個(gè)問(wèn)題的一種解決方案。如果再通俗一點(diǎn)說(shuō),設(shè)計(jì)模式就是給面向?qū)ο筌浖_(kāi)發(fā)中的一些好的設(shè)計(jì)取個(gè)名字。
GoF成員之一John Vlissides在他的另一本關(guān)于設(shè)計(jì)模式的著作《設(shè)計(jì)模式沉思錄》中寫(xiě)過(guò)這樣一段話:
設(shè)想有一個(gè)電子愛(ài)好者,雖然他沒(méi)有經(jīng)過(guò)正規(guī)的培訓(xùn),但是卻日積月累地設(shè)計(jì)并制造出許多有用的電子設(shè)備:業(yè)余無(wú)線電、蓋革計(jì)數(shù)器、報(bào)警器等。有一天這個(gè)愛(ài)好者決定重新回到學(xué)校去攻讀電子學(xué)學(xué)位,來(lái)讓自己的才能得到真實(shí)的認(rèn)可。隨著課程的展開(kāi),這個(gè)愛(ài)好者突然發(fā)現(xiàn)課程內(nèi)容都似曾相識(shí)。似曾相識(shí)的并不是術(shù)語(yǔ)或者表述的方式,而是背后的概念。這個(gè)愛(ài)好者不斷學(xué)到一些名稱和原理,雖然這些名稱和原理原來(lái)他不知道,但事實(shí)上他多年來(lái)一直都在使用。整個(gè)過(guò)程只不過(guò)是一個(gè)接一個(gè)的頓悟。
軟件開(kāi)發(fā)中的設(shè)計(jì)也是如此。這些“好的設(shè)計(jì)”并不是GoF發(fā)明的,而是早已存在于軟件開(kāi)發(fā)中。一個(gè)稍有經(jīng)驗(yàn)的程序員也許在不知不覺(jué)中數(shù)次使用過(guò)這些設(shè)計(jì)模式。GoF最大的功績(jī)是把這些“好的設(shè)計(jì)”從浩瀚的面向?qū)ο笫澜缰刑暨x出來(lái),并且給予它們一個(gè)好聽(tīng)又好記的名字。
那么,給模式一個(gè)名字有什么意義呢?上述故事中的電子愛(ài)好者在未進(jìn)入學(xué)校之前,一點(diǎn)都不知道這些關(guān)于電器的概念有一些特定的名稱,但這不妨礙他制造出一些電子設(shè)備。
實(shí)際上給“模式”取名的意義非常重要。人類(lèi)可以走到生物鏈頂端的前兩個(gè)原因分別是會(huì)“使用名字”和“使用工具”。在軟件設(shè)計(jì)中,一個(gè)好的設(shè)計(jì)方案有了名字之后,才能被更好地傳播,人們才有更多的機(jī)會(huì)去分享和學(xué)習(xí)它們。
也許這個(gè)小故事可以說(shuō)明名字對(duì)于模式的重要性:假設(shè)你是一名足球教練,正在球場(chǎng)邊指揮一場(chǎng)足球賽。通過(guò)一段時(shí)間的觀察后,發(fā)現(xiàn)對(duì)方的后衛(wèi)技術(shù)精湛,身體強(qiáng)壯,但邊后衛(wèi)速度較慢,中后衛(wèi)身高和頭球都非常一般。于是你在場(chǎng)邊大聲指揮隊(duì)員:“用速度突破對(duì)方邊后衛(wèi)之后,往球門(mén)方向踢出高球,中路接應(yīng)隊(duì)員搶點(diǎn)頭球攻門(mén)。”
在機(jī)會(huì)稍縱即逝的足球場(chǎng)上,教練這樣費(fèi)盡口舌地指揮隊(duì)員比賽無(wú)疑是荒謬的。實(shí)際上這種戰(zhàn)術(shù)有一個(gè)名字叫作“下底傳中”。正因?yàn)閼?zhàn)術(shù)有了對(duì)應(yīng)的名字,在球場(chǎng)上教練可以很方便地和球員交流。“下底傳中”這種戰(zhàn)術(shù)即是足球場(chǎng)上的一種“模式”。
在軟件設(shè)計(jì)中亦是如此。我們都知道設(shè)計(jì)經(jīng)驗(yàn)非常重要。也許我們都有過(guò)這種感覺(jué):這個(gè)問(wèn)題發(fā)生的場(chǎng)景似曾相識(shí),以前我遇到并解決過(guò)這個(gè)問(wèn)題,但是我不知道怎么跟別人去描述它。我們非常希望給這個(gè)問(wèn)題出現(xiàn)的場(chǎng)景和解決方案取一個(gè)統(tǒng)一的名字,當(dāng)別人聽(tīng)到這個(gè)名字的時(shí)候,便知道我想表達(dá)什么。比如一個(gè)JavaScript新手今天學(xué)會(huì)了編寫(xiě)each函數(shù),each函數(shù)用來(lái)迭代一個(gè)數(shù)組。他很難想到這個(gè)each函數(shù)其實(shí)就是迭代器模式。于是他向別人描述這個(gè)函數(shù)結(jié)構(gòu)和意圖的時(shí)候會(huì)遇到困難,而一旦大家對(duì)迭代器模式這個(gè)名字達(dá)成了共識(shí),剩下的交流便是自然而然的事情。
學(xué)習(xí)模式的作用
小說(shuō)家很少?gòu)念^開(kāi)始設(shè)計(jì)劇情,足球教練也很少?gòu)念^開(kāi)始發(fā)明戰(zhàn)術(shù),他們總是沿襲一些已經(jīng)存在的模式。當(dāng)足球教練看到對(duì)方邊后衛(wèi)速度慢,中后衛(wèi)身高矮時(shí),自然會(huì)想到“下底傳中”這種模式。
同樣,在軟件設(shè)計(jì)中,模式是一些經(jīng)過(guò)了大量實(shí)際項(xiàng)目驗(yàn)證的優(yōu)秀解決方案。熟悉這些模式的程序員,對(duì)某些模式的理解也許形成了條件反射。當(dāng)合適的場(chǎng)景出現(xiàn)時(shí),他們可以很快地找到某種模式作為解決方案。
比如,當(dāng)他們看到系統(tǒng)中存在一些大量的相似對(duì)象,這些對(duì)象給系統(tǒng)的內(nèi)存帶來(lái)了較大的負(fù)擔(dān)。如果他們熟悉享元模式,那么第一時(shí)間就可以想到用享元模式來(lái)優(yōu)化這個(gè)系統(tǒng)。再比如,系統(tǒng)中某個(gè)接口的結(jié)構(gòu)已經(jīng)不能符合目前的需求,但他們又不想去改動(dòng)這個(gè)被灰塵遮住的老接口,一個(gè)熟悉模式的程序員將很快地找到適配器模式來(lái)解決這個(gè)問(wèn)題。
如果我們還沒(méi)有學(xué)習(xí)全部的模式,當(dāng)遇到一個(gè)問(wèn)題時(shí),我們冥冥之中覺(jué)得這個(gè)問(wèn)題出現(xiàn)的幾率很高,說(shuō)不定別人也遇到過(guò)同樣的問(wèn)題,并且已經(jīng)把它整理成了模式,提供了一種通用的解決方案。這時(shí)候去翻翻《設(shè)計(jì)模式》這本書(shū)也許就會(huì)有意外的收獲。
模式在不同語(yǔ)言之間的區(qū)別
《設(shè)計(jì)模式》一書(shū)的副標(biāo)題是“可復(fù)用面向?qū)ο筌浖幕A(chǔ)”。《設(shè)計(jì)模式》這本書(shū)完全是從面向?qū)ο笤O(shè)計(jì)的角度出發(fā)的,通過(guò)對(duì)封裝、繼承、多態(tài)、組合等技術(shù)的反復(fù)使用,提煉出一些可重復(fù)使用的面向?qū)ο笤O(shè)計(jì)技巧。所以有一種說(shuō)法是設(shè)計(jì)模式僅僅是就面向?qū)ο蟮恼Z(yǔ)言而言的。
《設(shè)計(jì)模式》最初講的確實(shí)是靜態(tài)類(lèi)型語(yǔ)言中的設(shè)計(jì)模式,原書(shū)大部分代碼由C++寫(xiě)成,但設(shè)計(jì)模式實(shí)際上是解決某些問(wèn)題的一種思想,與具體使用的語(yǔ)言無(wú)關(guān)。模式社區(qū)和語(yǔ)言一直都在發(fā)展,如今,除了主流的面向?qū)ο笳Z(yǔ)言,函數(shù)式語(yǔ)言的發(fā)展也非常迅猛。在函數(shù)式或者其他編程范型的語(yǔ)言中,設(shè)計(jì)模式依然存在。
人類(lèi)飛上天空需要借助飛機(jī)等工具,而鳥(niǎo)兒天生就有翅膀。在Dota游戲里,牛頭人的人生目標(biāo)是買(mǎi)一把跳刀(跳刀可以使用跳躍技能),而敵法師天生就有跳躍技能。因?yàn)檎Z(yǔ)言的不同,一些設(shè)計(jì)模式在另外一些語(yǔ)言中的實(shí)現(xiàn)也許跟我們?cè)凇对O(shè)計(jì)模式》一書(shū)中看到的大相徑庭,這一點(diǎn)也不令人意外。
Google的研究總監(jiān)Peter Norvig早在1996年一篇名為“動(dòng)態(tài)語(yǔ)言設(shè)計(jì)模式”的演講中,就指出了GoF所提出的23種設(shè)計(jì)模式,其中有16種在Lisp語(yǔ)言中已經(jīng)是天然的實(shí)現(xiàn)。比如,Command模式在Java中需要一個(gè)命令類(lèi),一個(gè)接收者類(lèi),一個(gè)調(diào)用者類(lèi)。Command模式把運(yùn)算塊封裝在命令對(duì)象的方法內(nèi),成為該對(duì)象的行為,并把命令對(duì)象四處傳遞。但在Lisp或者JavaScript這些把函數(shù)當(dāng)作一等對(duì)象的語(yǔ)言中,函數(shù)便能封裝運(yùn)算塊,并且函數(shù)可以被當(dāng)成對(duì)象一樣四處傳遞,這樣一來(lái),命令模式在Lisp或者JavaScript中就成為了一種隱形的模式。
在Java這種靜態(tài)編譯型語(yǔ)言中,無(wú)法動(dòng)態(tài)地給已存在的對(duì)象添加職責(zé),所以一般通過(guò)包裝類(lèi)的方式來(lái)實(shí)現(xiàn)裝飾者模式。但在JavaScript這種動(dòng)態(tài)解釋型語(yǔ)言中,給對(duì)象動(dòng)態(tài)添加職責(zé)是再簡(jiǎn)單不過(guò)的事情。這就造成了JavaScript語(yǔ)言的裝飾者模式不再關(guān)注于給對(duì)象動(dòng)態(tài)添加職責(zé),而是關(guān)注于給函數(shù)動(dòng)態(tài)添加職責(zé)。
設(shè)計(jì)模式的適用性
設(shè)計(jì)模式被一些人認(rèn)為只是夸夸其談的東西,這些人認(rèn)為設(shè)計(jì)模式并沒(méi)有多大用途。畢竟我們用普通的方法就能解決的問(wèn)題,使用設(shè)計(jì)模式可能會(huì)增加復(fù)雜度,或帶來(lái)一些額外的代碼。如果對(duì)一些設(shè)計(jì)模式使用不當(dāng),事情還可能變得更糟。
從某些角度來(lái)看,設(shè)計(jì)模式確實(shí)有可能帶來(lái)代碼量的增加,或許也會(huì)把系統(tǒng)的邏輯搞得更復(fù)雜。但軟件開(kāi)發(fā)的成本并非全部在開(kāi)發(fā)階段,設(shè)計(jì)模式的作用是讓人們寫(xiě)出可復(fù)用和可維護(hù)性高的程序。假設(shè)有一個(gè)空房間,我們要日復(fù)一日地往里面放一些東西。最簡(jiǎn)單的辦法當(dāng)然是把這些東西直接扔進(jìn)去,但是時(shí)間久了,就會(huì)發(fā)現(xiàn)很難從這個(gè)房子里找到自己想要的東西,要調(diào)整某幾樣?xùn)|西的位置也不容易。所以在房間里做一些柜子也許是個(gè)更好的選擇,雖然柜子會(huì)增加我們的成本,但它可以在維護(hù)階段為我們帶來(lái)好處。使用這些柜子存放東西的規(guī)則,或許就是一種模式。
所有設(shè)計(jì)模式的實(shí)現(xiàn)都遵循一條原則,即“找出程序中變化的地方,并將變化封裝起來(lái)”。一個(gè)程序的設(shè)計(jì)總是可以分為可變的部分和不變的部分。當(dāng)我們找出可變的部分,并且把這些部分封裝起來(lái),那么剩下的就是不變和穩(wěn)定的部分。這些不變和穩(wěn)定的部分是非常容易復(fù)用的。這也是設(shè)計(jì)模式為什么描寫(xiě)的是可復(fù)用面向?qū)ο筌浖A(chǔ)的原因。
設(shè)計(jì)模式被人誤解的一個(gè)重要原因是人們對(duì)它的誤用和濫用,比如將一些模式用在了錯(cuò)誤的場(chǎng)景中,或者說(shuō)在不該使用模式的地方刻意使用模式。特別是初學(xué)者在剛學(xué)會(huì)使用一個(gè)模式時(shí),恨不得把所有的代碼都用這個(gè)模式來(lái)實(shí)現(xiàn)。錘子理論在這里體現(xiàn)得很明顯:當(dāng)我們有了一把錘子,看什么都是釘子。拿足球比賽的例子來(lái)說(shuō),我們的目標(biāo)只是進(jìn)球,“下底傳中”這種“模式”僅僅是達(dá)到進(jìn)球目標(biāo)的一種手段。當(dāng)我們面臨密集防守時(shí),下底傳中或許是一種好的選擇;但如果我們的球員獲得了一個(gè)直接面對(duì)對(duì)方守門(mén)員的單刀機(jī)會(huì),那么是否還要把球先傳向邊路隊(duì)友,再由邊路隊(duì)友來(lái)一個(gè)邊路傳中呢?答案是顯而易見(jiàn)的,模式應(yīng)該用在正確的地方。而哪些才算正確的地方,只有在我們深刻理解了模式的意圖之后,再結(jié)合項(xiàng)目的實(shí)際場(chǎng)景才會(huì)知道。
分辨模式的關(guān)鍵是意圖而不是結(jié)構(gòu)
在設(shè)計(jì)模式的學(xué)習(xí)中,有人經(jīng)常發(fā)出這樣的疑問(wèn):代理模式和裝飾者模式,策略模式和狀態(tài)模式,策略模式和智能命令模式,這些模式的類(lèi)圖看起來(lái)幾乎一模一樣,它們到底有什么區(qū)別?
實(shí)際上這種情況是普遍存在的,許多模式的類(lèi)圖看起來(lái)都差不多,模式只有放在具體的環(huán)境下才有意義。比如我們的手機(jī),把它當(dāng)電話的時(shí)候,它就是電話;把它當(dāng)鬧鐘的時(shí)候,它就是鬧鐘;用它玩游戲的時(shí)候,它就是游戲機(jī)。我看到有人手中拿著iPhone18,但那實(shí)際上可能只是一個(gè)吹風(fēng)機(jī)。有很多模式的類(lèi)圖和結(jié)構(gòu)確實(shí)很相似,但這不太重要,辨別模式的關(guān)鍵是這個(gè)模式出現(xiàn)的場(chǎng)景,以及為我們解決了什么問(wèn)題。
對(duì)JavaScript設(shè)計(jì)模式的誤解
雖然JavaScript是一門(mén)完全面向?qū)ο蟮恼Z(yǔ)言,但在很長(zhǎng)一段時(shí)間內(nèi),JavaScript在人們的印象中只是用來(lái)驗(yàn)證表單,或者完成一些簡(jiǎn)單動(dòng)畫(huà)特效的腳本語(yǔ)言。在JavaScript語(yǔ)言上運(yùn)用設(shè)計(jì)模式難免顯得小題大做。但目前JavaScript已成為最流行的語(yǔ)言之一,在許多大型Web項(xiàng)目中,JavaScript代碼的數(shù)量已經(jīng)非常多了。我們絕對(duì)有必要把一些優(yōu)秀的設(shè)計(jì)模式借鑒到JavaScript這門(mén)語(yǔ)言中。許多優(yōu)秀的JavaScript開(kāi)源框架也運(yùn)用了不少設(shè)計(jì)模式。
JavaScript設(shè)計(jì)模式的社區(qū)目前還幾乎是一片荒漠。網(wǎng)絡(luò)上有一些討論JavaScript設(shè)計(jì)模式的資料和文章,但這些資料和文章大多都存在兩個(gè)問(wèn)題。
第一個(gè)問(wèn)題是習(xí)慣把靜態(tài)類(lèi)型語(yǔ)言的設(shè)計(jì)模式照搬到JavaScript中,比如有人為了模擬JavaScript版本的工廠方法(Factory Method)模式,而生硬地把創(chuàng)建對(duì)象的步驟延遲到子類(lèi)中。實(shí)際上,在Java等靜態(tài)類(lèi)型語(yǔ)言中,讓子類(lèi)來(lái)“決定”創(chuàng)建何種對(duì)象的原因是為了讓程序迎合依賴倒置原則(DIP)。在這些語(yǔ)言中創(chuàng)建對(duì)象時(shí),先解開(kāi)對(duì)象類(lèi)型之間的耦合關(guān)系非常重要,這樣才有機(jī)會(huì)在將來(lái)讓對(duì)象表現(xiàn)出多態(tài)性。
而在JavaScript這種類(lèi)型模糊的語(yǔ)言中,對(duì)象多態(tài)性是天生的,一個(gè)變量既可以指向一個(gè)類(lèi),又可以隨時(shí)指向另外一個(gè)類(lèi)。JavaScript不存在類(lèi)型耦合的問(wèn)題,自然也沒(méi)有必要刻意去把對(duì)象“延遲”到子類(lèi)創(chuàng)建,也就是說(shuō),JavaScript實(shí)際上是不需要工廠方法模式的。模式的存在首先是能為我們解決什么問(wèn)題,這種牽強(qiáng)的模擬只會(huì)讓人覺(jué)得設(shè)計(jì)模式既難懂又沒(méi)什么用處。
另一個(gè)問(wèn)題是習(xí)慣根據(jù)模式的名字去臆測(cè)該模式的一切。比如命令模式本意是把請(qǐng)求封裝到對(duì)象中,利用命令模式可以解開(kāi)請(qǐng)求發(fā)送者和請(qǐng)求接受者之間的耦合關(guān)系。但命令模式經(jīng)常被人誤解為只是一個(gè)名為execute的普通方法調(diào)用。這個(gè)方法除了叫作execute之外,其實(shí)并沒(méi)有看出其他用處。所以許多人會(huì)誤會(huì)命令模式的意圖,以為它其實(shí)沒(méi)什么用處,從而聯(lián)想到其他設(shè)計(jì)模式也沒(méi)有用處。
這些誤解都影響了設(shè)計(jì)模式在JavaScript語(yǔ)言中的發(fā)展。
模式的發(fā)展
前面說(shuō)過(guò),模式的社區(qū)一直在發(fā)展。GoF在1995年提出了23種設(shè)計(jì)模式。但模式不僅僅局限于這23種。在近20年的時(shí)間里,也許有更多的模式已經(jīng)被人發(fā)現(xiàn)并總結(jié)了出來(lái)。比如一些JavaScript圖書(shū)中會(huì)提到模塊模式、沙箱模式等。這些“模式”能否被世人公認(rèn)并流傳下來(lái),還有待時(shí)間驗(yàn)證。不過(guò)某種解決方案要成為一種模式,還是有幾個(gè)原則要遵守的。這幾個(gè)原則即是“再現(xiàn)”“教學(xué)”和“能夠以一個(gè)名字來(lái)描述這種模式”。
不管怎樣,在一些模式被公認(rèn)并流行起來(lái)之前,需要慎重地冠之以某種模式的名稱。否則模式也許很容易泛濫,導(dǎo)致人人都在發(fā)明模式,這反而增加了交流的難度。說(shuō)不準(zhǔn)哪天我們就能聽(tīng)到有人說(shuō)全局變量模式、加模式、減模式等。
在《設(shè)計(jì)模式》出版后的近20年里,也出現(xiàn)了另外一批講述設(shè)計(jì)模式的優(yōu)秀讀物。其中許多都獲得過(guò)Jolt大獎(jiǎng)。數(shù)不清的程序員從設(shè)計(jì)模式中獲益,也許是改善了自己編寫(xiě)的某個(gè)軟件,也許是從設(shè)計(jì)模式的學(xué)習(xí)中更好地理解了面向?qū)ο缶幊趟枷搿o(wú)論如何,相信對(duì)我們這些大多數(shù)的普通程序員來(lái)說(shuō),系統(tǒng)地學(xué)習(xí)設(shè)計(jì)模式并沒(méi)有壞處,相反,你會(huì)在模式的學(xué)習(xí)過(guò)程中受益匪淺。
- C程序設(shè)計(jì)簡(jiǎn)明教程(第二版)
- TensorFlow Lite移動(dòng)端深度學(xué)習(xí)
- 數(shù)據(jù)結(jié)構(gòu)(Java語(yǔ)言描述)
- 程序員修煉之道:通向務(wù)實(shí)的最高境界(第2版)
- Java:High-Performance Apps with Java 9
- Visual Basic程序設(shè)計(jì)
- Mastering Android Game Development
- Java零基礎(chǔ)實(shí)戰(zhàn)
- Mastering Python Design Patterns
- 計(jì)算機(jī)應(yīng)用基礎(chǔ)教程(Windows 7+Office 2010)
- FFmpeg開(kāi)發(fā)實(shí)戰(zhàn):從零基礎(chǔ)到短視頻上線
- Solutions Architect's Handbook
- Angular Design Patterns
- The Python Apprentice
- ROS Robotics Projects