開放封閉原則定義
開放與封閉原則有兩種不同的定義,分別是20世紀80年代最原始的定義和後期一個更現代的定義,後者對前者進行更加詳盡的闡述。
Meyer的定義
軟體實體應該允許擴充,但禁止修改
——《物件導向軟體構造》
Martin的定義
”對於擴充是開放的。“ 這意味著模組的行為是可以擴充的。當應用程式的需求改變時,我們可以對其模組進行擴充,使其具有滿足那些需求變更的新行為。換句話說,我們可以改變模組的功能。
“對於修改是封閉的。“ 對模組行為進行擴充時,不必改動該模組的原始碼或二進位代碼。模組的二進位可執行版本,無論是可連結的庫、DLL或Java的.jar檔案,都無需改動。
——《敏捷式軟體開發 (Agile Software Development):原則、模式與實踐》
對於修改是封閉的
需要注意的是,“對於修改是封閉的”有兩個例外:
1.修複缺陷所做的改動
2.用戶端無法感知到的改動
缺陷修複
缺陷在軟體中很常見,是不可能完全消除的。當缺陷出現時,就需要我們修複現有的代碼。軟體修複明顯傾向於實用主義而不是堅持開放封閉原則。
用戶端感知
如果一個類的改動會引起另一個類的改動,那麼這兩個類就是緊密耦合的。相反,如果一個類的修改總是獨立的,並不會引起其他類的改動,那麼這些類就是鬆散耦合的。我們要記住,任何情況下,鬆散耦合都比緊密耦合要好。如果我們對現有代碼的修改不會影響用戶端代碼,那麼也就談不上違背開放封閉原則。
對於擴充是開放的擴充點沒有擴充點
TradeProcessorClient類直接依賴TradeProcessor類。當接到一個需要改動TradeProcessor類的新需求時,為了不改變原有的類型,建立了一個新類型(TradeProcessor2)來實現需求提出的新功能。但是這種改動帶來的副作用就是必須改動TradeProcessorClient類,這樣才能依賴的新的TradeProcessor2類。
如果對現有代碼的改動不會影響用戶端,那就不需要建立新類型。但是如果對現有代碼的改動改變了TradeProcessor類方法的簽名,那就不是簡單的對類實現的改動,而是對介面的改動了。因為用戶端總是與服務的介面緊密耦合的,所以任何介面上的改動都會引起用戶端代碼的改動。
虛方法
TradeProcessor類的另一種實現包含了一個擴充點:ProcessTrades是個虛方法。
任何一個帶有虛方法成員的類都是對外開放的,這種擴充是通過繼承做到的。可以修改其子類的ProcessTrades方法而無需改變原有的TradeProcessor類源碼。此時的TradeProcessorClient類也不需要做改動,可以使用多態向用戶端提供新版本的TradeProcessor2類的執行個體。
但是使用虛方法能重新實現的範圍是有一定限制的。在子類中可以訪問基類,因此可以直接調用TradeProcessor類的ProcessTrades方法,但是無法改動該方法內的任何代碼。要麼在子類方法裡調用基類同名方法並在其前後實現新的特性,要麼完全重新實現子類的方法。虛方法沒有中間狀態。另外子類只能訪問基類的受保護和公用成員,如果基類中有很多子類無權訪問的私人成員,可能就需要修改基類的實現了。但是,這又會違背開放封閉原則。
抽象方法
另外一種使用實現繼承的更加靈活的擴充點是抽象方法。
用戶端依賴抽象基類,因此提供任何一個具體子類(或者用來支援新需求的子類)給用戶端都不會違背開放封閉原則。
介面繼承
最後一個擴充點是實現繼承外的另外一種選項:介面繼承。用戶端委託介面取代了用戶端對類的依賴。
介面繼承要比實現繼承好很多。基於實現繼承,所有子類(現有的和將來的)都是基類的用戶端。給繼承圖頂部節點添加新成員的改動會影響到該層級結構下的所有成員,而介面要比類靈活的多。這當然不是說代表實現繼承的虛方法和抽象方法提供的擴充點沒有一點用處,但是它們的確無法提供與介面一樣強大的自適應能力。
防止變異
雖然我們已經知道了實現擴充點的方式,但是我們應該到處都留著擴充點嗎?防止變異是另外一個跟開放封閉原則相關的重要準則:
識別可預見的變化點並圍繞它們建立一個穩定的介面。
可預見的變化
要識別出很可能發生變更的需求或者實現起來特別麻煩的代碼部分,然後將它們隱藏在擴充點之後。
一個穩定的介面
依賴介面的最大優勢是介面變化的可能性要比實現小很多。用於表達擴充點的所有介面應該都是穩定的。因為用戶端是直接依賴介面的,如果介面發生變化,用戶端也必須做相應的改動。
最後
通過確保代碼對擴充開放對修改封閉,可以有效阻止後期變化對現有類的修改,因為後面的編碼人員只能在你預留的擴充點上掛靠新建立的類。代碼可以很死板,幾乎無法擴充和細化;代碼也可以很流暢,帶有足夠的準備應對新需求的大量擴充點。兩種選擇都沒有錯,只是要在具體的情境進行選擇和應用。
參考
《C#敏捷開發實踐》
CoderFocus
公眾號:
聲明:本文為博主學習感悟總結,水平有限,如果不當,歡迎指正。如果您認為還不錯,不妨點擊一下下方的【推薦】按鈕,謝謝支援。與引用請註明作者及出處。