JavaScript教程 | JavaScript設計模式
什么是設計模式?
設計模式這個概念是由一本名為《設計模式:可復用面向?qū)ο筌浖幕A》推廣而來, 這本書在1994年由四個C++工程師編寫。
這本書探討了面向?qū)ο蟮木幊痰哪芰拖葳?,并介紹了23種可以用來解決編程問題的模式。
這些模式并不是算法或者具體的實現(xiàn)。它們更像是想法、觀點和抽象,輔助你去解決一些特定問題。
根據(jù)要素的不同模式的實現(xiàn)也各不相同,重要的是模式背后的概念,它可以幫助我們更好地解決問題。
話雖如此,但是請記住,這些模式建立在C++的OOP的基礎之上,當使用更現(xiàn)代的編程語言如JavaScript時,模式可能不等效,甚至給代碼添加了不必要的樣本。
不過把這些模式當作一般的編程知識來了解沒有壞處。
旁注:如果你不熟悉編程范式或者OOP,推薦你閱讀我最近寫的這兩篇文章。 ??
設計模式的簡介就到這里。設計模式可以被分為三大類:?創(chuàng)建、結(jié)構(gòu)、行為范例。讓我們逐個了解。 ??
創(chuàng)建范例
創(chuàng)建范例包括不同的創(chuàng)建對象的機制。
單例模式
單例模式確保對象的類只有一個不可更改實例。簡言之,單例模式包含一個不能被復制和修改的對象。當你希望應用遵循?"真理的單點性“?的觀點時,這個模式就能發(fā)揮作用。
比方說,我們想在一個單一對象中包含應用程序的所有配置,而且禁止對該對象進行任何復制或修改。
可以通過對象字面量和類者兩種方法來實現(xiàn):
使用對象的字面量
使用類
工廠方法
工廠方法提供創(chuàng)建對象的接口,對象被創(chuàng)建后可以修改。這樣做的好處是,創(chuàng)建對象的邏輯集中在一個地方,這樣簡化了代碼,使得代碼更易組織。
這種模式被大量應用??梢酝ㄟ^類和工廠函數(shù)(返回對象的函數(shù))來實現(xiàn):
使用類
使用工廠函數(shù)
抽象工廠
抽象工廠?允許在不指定具體類的情況下生成一系列相關(guān)的對象。當你想要創(chuàng)建僅共享某些屬性和方法的對象時,抽象工廠模式就可以派上用場。
它的工作方式是給客戶端提供一個可以交互的抽象工廠。抽象工廠通過特定邏輯調(diào)用具體工廠,具體工廠返回最終的對象。
這樣做給工廠模式添加了一個抽象層,我們通過僅和單個工廠函數(shù)或者類交互來創(chuàng)建各種不同類型的對象。
讓我們來看幾個例子。 假設我們是汽車公司,我們除了生產(chǎn)小汽車以外,還生產(chǎn)摩托車和卡車。
構(gòu)造器
構(gòu)造器模式分“步驟”創(chuàng)建對象。通常我們通過不同的函數(shù)和方法向?qū)ο筇砑訉傩院头椒ā?/p>
構(gòu)造器的好處在于通過不同實體分開創(chuàng)建屬性和方法。
通過類或者構(gòu)造函數(shù)創(chuàng)建的實例通常繼承了所有的屬性和方法,但是如果使用構(gòu)造器,我們可以只應用我們需要的“步驟”來創(chuàng)建對象,這樣就更靈活。
這個概念和對象組合相關(guān), 我在這篇文章討論過這個話題。
原型
原型允許把一個對象作為藍圖創(chuàng)建另一個對象,新對象繼承原對象的屬性和方法。
如果你已經(jīng)使用過一段時間的JavaScript,你應該對原型繼承有一定了解。
原型鏈繼承的結(jié)果和使用類相似,只是更為靈活,因為屬性和方法可以不通過同一個類在對象之間共享。
結(jié)構(gòu)范例
結(jié)構(gòu)范例將對象和類組合成更大的結(jié)構(gòu)。
適配器
適配器允許兩個接口不兼容的對象相互交互。
假設你的應用程序調(diào)用一個API并會返回一個XML,然后將結(jié)果發(fā)送給另一個API來處理信息,但是處理信息的API期待的是JSON格式。因為格式不兼容,所以你不能直接發(fā)送信息,需要先?適配?結(jié)果。 ??
我們可以舉一個更簡單的例子來具象化這個概念。假設我們有一個以城市為元素的數(shù)組,以及一個可以返回擁有最多人口城市的函數(shù)。數(shù)組中的城市人口以百萬為單位計數(shù),但是有一個新城市的人口單位不是百萬:
裝飾
裝飾通過增加一個修飾對象來包裹原來的對象,從而給原來的對象添加新的行為。 如果你熟悉React或者高階組件(HOC),你內(nèi)心的小鈴鐺可能會叮當一下。
從技術(shù)上講,React中的組件是函數(shù)而不是對象。但如果你仔細思索React上下文(React Context)或者Memo是怎么運作的,你會發(fā)現(xiàn)我們將組件作為子組件傳入HOC后,子組件而可以訪問某些功能。
在下面的例子里中ContextProvider組件接受子組件作為prop:
然后我們包裹整個應用:
這個例子可能不是書的作者在寫這個模式時想到的確切實現(xiàn),但我相信想法是一樣的:把一個對象放在另一個對象中,這樣它就可以訪問某些功能。;)
外觀
外觀模式給庫、框架以及其他復雜的類集提供簡化的接口。
嗯……我們可以舉的例子非常多,不是嗎?React本身以及各種各樣的軟件開發(fā)相關(guān)的庫就是基于這個模式。特別是當你思考聲明式編程,會發(fā)現(xiàn)這個范式就是使用抽象的方法對開發(fā)者隱藏復雜性。
JavaScript中的?map
、?sort
、?reduce
?和?filter
函數(shù)都是很好的例子,這些函數(shù)的背后其實是我們的老朋友for
循環(huán)。
另一個例子是一些UI庫,如:MUI。正如以下示例所展現(xiàn)的這樣,庫提供了組件,組件帶來了內(nèi)置特性和功能,幫助我們更快、更輕松地構(gòu)建代碼。
這些代碼最后都會編譯成簡單的HTML元素,這是瀏覽器唯一能理解的東西。組件只是采用了抽象的辦法,使得我們的編碼過程更容易。
一個外觀模式...
代理
代理模式為另一個對象提供替代或者占位符。這個想法是控制對原始對象的訪問,當請求到達實際的原始對象之前或者之后再執(zhí)行某種操作。
如果你熟悉ExpressJS的話,這個概念就不陌生。Express是用于開發(fā)NodeJS API的框架,其中一個功能就是中間件的使用。中間件是我們可以在請求到達終點之前、之中和之后執(zhí)行的一段代碼。
讓我們看一個例子。是一個驗證身份令牌的函數(shù),不用太關(guān)注驗證是如何實現(xiàn)的,但是要注意函數(shù)接受令牌作為參數(shù),一旦驗證完畢就會調(diào)用next()
函數(shù)。
這個函數(shù)就是一個中間件,我們可以API中的任意終點使用這個中間件。只需要將其添加在終點地址之后,終點的函數(shù)聲明之前:
如果沒有提供令牌或者提供了錯誤的令牌,中間件就會返回相應的錯誤響應。如果提供了有效令牌,中間件將調(diào)用next()
函數(shù),然后將執(zhí)行終點函數(shù)。
我們可以在終點內(nèi)部編寫相同的代碼來驗證令牌,這樣就用不著中間件了,但使用了抽象的方法,我們可以在不同的終點復用中間件。 ??
同樣這個例子可能不是作者的確切想法,但我相信這是一個有效的例子。我們控制對象的訪問,以便我們可以在特定時刻執(zhí)行操作。
行為范式
行為范式控制不同對象之間的通訊。
責任鏈
責任鏈將請求通過處理鏈傳遞,鏈條上的每一個處理程序決定要么處理請求,要么將請求傳遞給鏈條上的下一個處理程序。
我們可以使用之前示例來演示這個模式,因為Express的中間件就是一種處理程序,要么處理請求,要么將其傳遞給下一個處理程序。
如果你想要另一個示例,可以考慮任何需要通過步驟來一步一步實現(xiàn)信息處理的系統(tǒng)。在每個步驟中,不同的實體負責執(zhí)行操作,并且只有在滿足特定條件時,信息才會傳遞給另一個實體。
需要使用API的前端應用程序就是很好的例子:
有一個負責渲染UI的函數(shù)
一旦渲染,另一個函數(shù)向API終點發(fā)出請求
如果終點響應符合預期,則將信息傳遞給另一個函數(shù),該函數(shù)以給定方式對數(shù)據(jù)進行排序并存儲在變量中
一旦變量存儲了所需的信息,另一個函數(shù)負責在UI中呈現(xiàn)它。
可以看到這里有許多不同的實體協(xié)作執(zhí)行任務。每個都負責該任務的一個“步驟”,這有助于代碼模塊化和關(guān)注點分離。????
迭代器
迭代器用于遍歷集合的元素。這在現(xiàn)代編程語言中顯得微不足道,但并非如此。
JavaScript內(nèi)置函數(shù)(for
、forEach
、for...of
、for...in
、map
、reduce
、filter
等)就是手邊可以拿來遍歷數(shù)據(jù)結(jié)構(gòu)的方法。
遍歷算法?以及更為復雜的樹和圖這樣的數(shù)據(jù)結(jié)構(gòu)使用的代碼也是迭代器的例子。
觀察者
觀察者模式允許你定義一個訂閱機制來通知多個對象他們正在觀察的對象發(fā)生的任何事件?;旧?,這就像在給定對象上有一個事件偵聽器,當該對象執(zhí)行我們正在偵聽的操作時,我們會采取一些行動。
React的useEffect鉤子就是一個很好的例子。 useEffect在我們聲明的那一刻執(zhí)行給定的函數(shù)。
鉤子分為兩個主要部分:可執(zhí)行函數(shù)和依賴數(shù)組。如果數(shù)組為空,如下例所示,每次渲染組件時都會執(zhí)行該函數(shù)。
如果在依賴數(shù)組中聲明任何變量,則該函數(shù)將僅在這些變量發(fā)生變化時執(zhí)行。
也可以將JavaScript的事件監(jiān)聽器視為觀察者模式。另外,響應式編程和庫如RxJS,用來處理異步信息和事件的方法也是這個模式。
更多學習資料關(guān)注馬哥教育? VX :Teemo--1