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