摘要:事件驅(qū)動(dòng)模型對(duì)于一些復(fù)雜的事件驅(qū)動(dòng)模型,比如拖拽,往往使用開閉原則會(huì)達(dá)到意想不到的效果。
這是理解SOLID原則,介紹什么是開閉原則以及它為什么能夠在對(duì)已有的軟件系統(tǒng)或者模塊提供新功能時(shí),避免不必要的更改(重復(fù)勞動(dòng))。開閉原則是什么
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.軟件實(shí)體(類、模塊、函數(shù)等)都應(yīng)當(dāng)對(duì)擴(kuò)展具有開放性,但是對(duì)于修改具有封閉性。
首先,我們假設(shè)在代碼中,我們已經(jīng)有了若干抽象層代碼,比如類、模塊、高階函數(shù),它們都僅做一件事(還記得單一職責(zé)原則嗎?),并且都做的十分出色,所以我們想讓它們始終處于簡潔、高內(nèi)聚并且好用的狀態(tài)。
但是另一方面,我們還是會(huì)面臨改變,這些改變包含范圍(譯者注:應(yīng)當(dāng)是指抽象模塊的職責(zé)范圍)的改變,新功能的增加請(qǐng)求還有新的業(yè)務(wù)邏輯需求。
所以對(duì)于上面我們所擁有的抽象層代碼,在長期想讓它處于一成不變的狀態(tài)是不現(xiàn)實(shí)的,你不可避免的會(huì)針對(duì)以上的需要作出改變的需求,增加更多的功能,增加更多的邏輯和交互。在上一篇文章,我們知道,改變會(huì)使系統(tǒng)復(fù)雜,復(fù)雜會(huì)促使模塊間的耦合性上升,所以我們迫切地需要尋找一種方法能夠使我們的抽象模塊不僅可以擴(kuò)大它的職責(zé)范圍,同時(shí)還能夠保持當(dāng)前良好的狀態(tài)(簡潔、高內(nèi)聚、好用)。
這便是開閉原則存在的意義,它能夠幫助我們完美地實(shí)現(xiàn)這一切。
如何實(shí)踐開閉原則當(dāng)你需要對(duì)已有代碼作出一些修改時(shí),請(qǐng)切記以下兩點(diǎn):
保持函數(shù)、類、模塊當(dāng)前它們本身的狀態(tài),或者是近似于它們一般情況下的狀態(tài)(即不可修改性)
使用組合的方式(避免使用繼承方式)來擴(kuò)展現(xiàn)有的類,函數(shù)或模塊,以使它們可能以不同的名稱來暴露新的特性或功能
這里關(guān)于繼承,我們特意增加了一個(gè)注釋,在這種情況下使用繼承可能會(huì)使模塊之間耦合在一起,同時(shí)這種耦合是可避免的,我們通常在一些預(yù)先有著良好定義的結(jié)構(gòu)上使用繼承。(譯者注:這里應(yīng)該是指,對(duì)于我們預(yù)先設(shè)計(jì)好的功能,推薦使用繼承方式,對(duì)于后續(xù)新增的變更需求,推薦使用組合方式)
舉個(gè)例子(譯者注:我對(duì)這里的例子做了一些修改,原文中并沒有詳細(xì)的說明)
interface IRunner { run: () => void; } class Runner implements IRunner { run(): void { console.log("9.78s"); } } interface IJumper { jump: () => void; } class Jumper implements IJumper { jump(): void { console.log("8.95,"); } }
例子中,我們首先聲明了一個(gè)IRunner接口,之后又聲明了IJumper,并分別實(shí)現(xiàn)了它們,并且實(shí)現(xiàn)類的職能都是單一的。
假如現(xiàn)在我們需要提供一個(gè)既會(huì)跑又會(huì)跳的對(duì)象,如果我們使用繼承的方式,可以這么寫
class RunnerAndJumper extends Runner { jump: () => void }
或者
class RunnerAndJumper extends Jumper { run: () => void }
但是使用繼承的方式會(huì)使這個(gè)RunnerAndJumper與Runner(或者Jumper)耦合在一起(耦合在一起的原因是因?yàn)樗鼤?huì)因它的父類改變而改變),我們?cè)賮碛媒M合的方式試試看,如下:
class RunnerAndJumper { private runnerClass: IRunner; private jumperClass: IJumper; constructor(runner: IRunner, jumper: IJumper) { this.runnerClass = new runner(); this.jumperClass = new jumper(); } run() { this.runnerClass.run(); } jump() { this.jumperClass.jump(); } }
我們?cè)?b>RunnerAndJumper的構(gòu)造函數(shù)中聲明兩個(gè)依賴,一個(gè)是IRunner類型,一個(gè)是IJumper類型。
最終的代碼其實(shí)和依賴倒置原則中的例子很像,而且你會(huì)發(fā)現(xiàn),RunnerAndJumper類本身并沒有與任何別的類耦合在一起,它的職能同樣是單一的,它是對(duì)一個(gè)即會(huì)跑又會(huì)跳的實(shí)體的抽象,并且這里我們還可以使用DI(依賴注入)技術(shù)進(jìn)一步的優(yōu)化我們的代碼,降低它的耦合度。
反思開閉原則所帶來最有用的好處就是,當(dāng)我們?cè)趯?shí)現(xiàn)我們的抽象層代碼時(shí),我們就可以對(duì)未來可能需要作出改變的地方擁有一個(gè)比較完整的設(shè)想,這樣當(dāng)我們真正面臨改變時(shí),我們所對(duì)原有代碼的修改,更貼近于改變本身,而不是一味的修改我們已有的抽象代碼。
在這種情況下,由于我們節(jié)省了不必要的勞動(dòng)和時(shí)間,我們就可以將更多的精力投入到關(guān)于更加長遠(yuǎn)的事宜計(jì)劃上面,而且可以針對(duì)這些事宜需要作出的改變,提前和團(tuán)隊(duì)溝通,最終給予一套更加健壯、更符合系統(tǒng)模塊本身的解決方案。
在整個(gè)軟件開發(fā)周期中(比如一個(gè)敏捷開發(fā)周期),你對(duì)于整個(gè)周期中的事情了解的越透徹、越多,則越好。身為一個(gè)工程師,在一個(gè)開發(fā)沖刺中,為了在沖刺截止日期結(jié)束前,實(shí)現(xiàn)一個(gè)高效的、可靠的系統(tǒng),你不會(huì)期望作出太多的改變,因此往往你可能會(huì)“偷工減料”。
從另一個(gè)角度來講,我們也應(yīng)當(dāng)致力于在每一次面臨需求變更的情況下,不需要一而再,再而三的更改我們已有的代碼。所有新的功能都應(yīng)當(dāng)通過增加一個(gè)新的組合類或方法實(shí)現(xiàn),或者通過復(fù)用已有的代碼來實(shí)現(xiàn)。
插件與中間件充分貫徹開閉原則的另一個(gè)例子,便是插件與中間件架構(gòu),我們可以從三個(gè)角度來簡單分析這種架構(gòu)是如何運(yùn)作的:
內(nèi)核或者容器:往往是核心功能的實(shí)現(xiàn)的前提,一般會(huì)成為整個(gè)系統(tǒng)最核心的部分
插件:在實(shí)現(xiàn)容器的基礎(chǔ)上,往往一些核心功能都是以內(nèi)置的插件實(shí)現(xiàn)的,并且,通過實(shí)現(xiàn)一套通用的網(wǎng)關(guān)類接口,我們可以使插件具有可插拔性,這樣在需要新增特性和功能時(shí),只需要實(shí)現(xiàn)新的插件并添加到容器即可,比如支持插件擴(kuò)展功能的瀏覽器Chrome。
中間件:中間件我們可以通過一個(gè)例子來說明,比如我們擁有一個(gè)請(qǐng)求 - 響應(yīng)周期,我們可以通過中間件,在周期中添加中間業(yè)務(wù)邏輯,以便為應(yīng)用程序提供額外的服務(wù)或橫切關(guān)注點(diǎn),比如Redux、express還有很多框架都支持這樣的功能。
總結(jié)希望這篇文章能夠幫助你學(xué)會(huì)如何應(yīng)用開閉原則并且從中收益。設(shè)計(jì)一個(gè)具有可組合性的系統(tǒng),同時(shí)提供具有良好定義的擴(kuò)展接口,是一種非常有用的技術(shù),這種技術(shù)最關(guān)鍵的地方在于,它使我們的系統(tǒng)能夠在保持強(qiáng)健的同時(shí),提供新功能、新特性,但是卻不會(huì)影響它當(dāng)前的狀態(tài)。
譯者注開閉原則是面向?qū)ο缶幊讨凶钪匾脑瓌t之一,有多重要呢?這么說吧,很多的設(shè)計(jì)原則和設(shè)計(jì)模式所希望達(dá)成的最終狀態(tài),往往符合開閉原則,因此許多原則都可以作為實(shí)現(xiàn)開閉原則的一種手段,在原文的例子中,我們可以很明顯的體會(huì)到,在實(shí)現(xiàn)開閉原則所提倡的理念的過程中,我們不經(jīng)意地使用之前兩篇文章中涉及的原則,比如:
保持對(duì)象的單一性(單一職責(zé))
實(shí)現(xiàn)依賴于抽象(依賴倒置原則)
我之前一直是做后端相關(guān)工作的,所以對(duì)于開閉原則接觸較早,這兩年轉(zhuǎn)行做了前端,隨著nodejs的發(fā)展,框架技術(shù)日新月異,但是其中脫穎而出的優(yōu)秀框架往往是充分貫徹了開閉原則,比如express、webpack還有狀態(tài)管理容器redux,它們均是開閉原則的最佳實(shí)踐。
另外一方面,在這兩年的工作也感受到,適當(dāng)?shù)氖褂煤瘮?shù)式編程的思想,往往是貫徹開閉原則一個(gè)比較好的開始,因?yàn)楹瘮?shù)式的編程中的核心概念之一便是compose(組合)。以函數(shù)式描述業(yè)務(wù)往往是原子級(jí)的指令,之后在需要描述更復(fù)雜的業(yè)務(wù)時(shí),我們復(fù)用并組合之前已經(jīng)存在的指令以達(dá)到目的,這恰恰符合開閉原則所提倡的可組合性。
最后再分享一些前端工作中,經(jīng)常需要使用開閉原則的最佳業(yè)務(wù)場(chǎng)景,
UI組件的表單組件:對(duì)于表單本身以容器來實(shí)現(xiàn),表單項(xiàng)以插件來實(shí)現(xiàn),這樣對(duì)于表單項(xiàng)如何渲染、如何加載、如何布局等功能,均會(huì)封閉與表單容器中,而對(duì)于表單項(xiàng)如何校驗(yàn)、如何取值、如何格式化等功能,則會(huì)開放與表單項(xiàng)容器中。
API服務(wù):一般我們可能會(huì)在項(xiàng)目中提供自定義修改請(qǐng)求頭部的工具方法,并在需要的時(shí)候調(diào)用。但這其實(shí)是一種比較笨的方法,如果可能的話,建議使用攔截器來完成這項(xiàng)任務(wù),不僅會(huì)提供代碼的可讀性,同時(shí)還會(huì)使發(fā)接口的業(yè)務(wù)層代碼保持封閉。
事件驅(qū)動(dòng)模型:對(duì)于一些復(fù)雜的事件驅(qū)動(dòng)模型,比如拖拽,往往使用開閉原則會(huì)達(dá)到意想不到的效果。最近有一個(gè)比較火的拖拽庫draggable,提供的拖拽體驗(yàn)相比其他同類型的庫簡直不是一個(gè)級(jí)別。我前段時(shí)間去讀它的源碼,發(fā)現(xiàn)它之所以強(qiáng)大,是因?yàn)樵谒鼉?nèi)部,針對(duì)多種拖拽事件,封裝了獨(dú)立的事件發(fā)射器(其內(nèi)部稱作Sensor),之后根據(jù)這些發(fā)射器指定了一套獨(dú)立的抽象事件驅(qū)動(dòng)模型,在這個(gè)模型基礎(chǔ)上,針對(duì)不同的業(yè)務(wù)場(chǎng)景提供不同的插件,比如:
原生拖拽(Draggable)
拖拽排序(Sortable)
拖拽放置(Droppable)
拖拽交換(Swappable)
還有若干提高用戶體驗(yàn)的其他插件,這一切均是以開閉原則而實(shí)現(xiàn)的。
能想到的大概就這么多,希望可以拋磚引玉,如有錯(cuò)誤,還望指正。
關(guān)注公眾號(hào) 全棧101,只談技術(shù),不談人生
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/107223.html
摘要:什么是里氏替換原則某個(gè)對(duì)象實(shí)例的子類實(shí)例應(yīng)當(dāng)可以在不影響程序正確性的基礎(chǔ)上替換它們。除了在編程語言層面,在前端實(shí)際工作中,你可能會(huì)聽到一個(gè)叫作的概念,這個(gè)概念我認(rèn)為也是里氏替換原則的一直延伸。 這是理解SOLID原則,關(guān)于里氏替換原則為什么提倡我們面向抽象層編程而不是具體實(shí)現(xiàn)層,以及為什么這樣可以使代碼更具維護(hù)性和復(fù)用性。 什么是里氏替換原則 Objects should be rep...
這是理解SOLID原則中,關(guān)于依賴倒置原則如何幫助我們編寫低耦合和可測(cè)試代碼的第一篇文章。 寫在前頭 當(dāng)我們?cè)谧x書,或者在和一些別的開發(fā)者聊天的時(shí)候,可能會(huì)談及或者聽到術(shù)語SOILD。在這些討論中,一些人會(huì)提及它的重要性,以及一個(gè)理想中的系統(tǒng),應(yīng)當(dāng)包含它所包含的5條原則的特性。 我們?cè)诿看蔚墓ぷ髦校憧赡軟]有那么多時(shí)間思考關(guān)于架構(gòu)這個(gè)比較大的概念,或者在有限的時(shí)間內(nèi)或督促下,你也沒有辦法實(shí)踐一些好...
摘要:開閉原則軟件實(shí)體類,模塊,函數(shù)應(yīng)該是可以擴(kuò)展的,而不是修改。函數(shù)并不符合開閉原則,因?yàn)橐坏┯行聞?dòng)物出現(xiàn),它需要修改代碼。 By Chidume Nnamdi | Oct 9, 2018 原文 面向?qū)ο蟮木幊填愋蜑檐浖_發(fā)帶來了新的設(shè)計(jì)。 這使開發(fā)人員能夠在一個(gè)類中組合具有相同目的/功能的數(shù)據(jù),來實(shí)現(xiàn)單獨(dú)的一個(gè)功能,不必關(guān)心整個(gè)應(yīng)用程序如何。 但是,這種面向?qū)ο蟮木幊踢€是會(huì)讓開發(fā)者困惑或...
摘要:六開閉原則開閉原則簡介開閉原則的英文名稱是,簡稱。開閉原則是面向?qū)ο笤O(shè)計(jì)中最基礎(chǔ)的設(shè)計(jì)原則,它指導(dǎo)我們?nèi)绾谓⒁粋€(gè)穩(wěn)定靈活的軟件系統(tǒng)。 面向?qū)ο蠡驹瓌t(3)- 最少知道原則與開閉原則 面向?qū)ο蠡驹瓌t(1)- 單一職責(zé)原則與接口隔離原則面向?qū)ο蠡驹瓌t(2)- 里式代換原則與依賴倒置原則面向?qū)ο蠡驹瓌t(3)- 最少知道原則與開閉原則 五、最少知道原則【迪米特法則】 1. 最少知道...
閱讀 2814·2023-04-26 02:00
閱讀 2771·2019-08-30 15:54
閱讀 860·2019-08-30 11:15
閱讀 1502·2019-08-29 15:31
閱讀 917·2019-08-29 14:12
閱讀 489·2019-08-29 13:08
閱讀 838·2019-08-27 10:51
閱讀 2706·2019-08-26 12:17