1. 程式人生 > >編碼最佳實踐——開放封閉原則

編碼最佳實踐——開放封閉原則

mark

開放封閉原則定義

開放與封閉原則有兩種不同的定義,分別是20世紀80年代最原始的定義和後期一個更現代的定義,後者對前者進行更加詳盡的闡述。

Meyer的定義

軟體實體應該允許擴充套件,但禁止修改

​ ——《面向物件軟體構造》

Martin的定義

”對於擴充套件是開放的。“ 這意味著模組的行為是可以擴充套件的。當應用程式的需求改變時,我們可以對其模組進行擴充套件,使其具有滿足那些需求變更的新行為。換句話說,我們可以改變模組的功能。

“對於修改是封閉的。“ 對模組行為進行擴充套件時,不必改動該模組的原始碼或二進位制程式碼。模組的二進位制可執行版本,無論是可連結的庫、DLL或Java的.jar檔案,都無需改動。

​ ——《敏捷軟體開發:原則、模式與實踐》

對於修改是封閉的

需要注意的是,“對於修改是封閉的”有兩個例外:

1.修復缺陷所做的改動

2.客戶端無法感知到的改動

缺陷修復

缺陷在軟體中很常見,是不可能完全消除的。當缺陷出現時,就需要我們修復現有的程式碼。軟體修復明顯傾向於實用主義而不是堅持開放封閉原則。

客戶端感知

如果一個類的改動會引起另一個類的改動,那麼這兩個類就是緊密耦合的。相反,如果一個類的修改總是獨立的,並不會引起其他類的改動,那麼這些類就是鬆散耦合的。我們要記住,任何情況下,鬆散耦合都比緊密耦合要好。如果我們對現有程式碼的修改不會影響客戶端程式碼,那麼也就談不上違背開放封閉原則。

對於擴充套件是開放的

擴充套件點

沒有擴充套件點

mark

TradeProcessorClient類直接依賴TradeProcessor類。當接到一個需要改動TradeProcessor類的新需求時,為了不改變原有的型別,建立了一個新型別(TradeProcessor2)來實現需求提出的新功能。但是這種改動帶來的副作用就是必須改動TradeProcessorClient類,這樣才能依賴的新的TradeProcessor2類。

如果對現有程式碼的改動不會影響客戶端,那就不需要建立新型別。但是如果對現有程式碼的改動改變了TradeProcessor類方法的簽名,那就不是簡單的對類實現的改動,而是對介面的改動了。因為客戶端總是與服務的介面緊密耦合的,所以任何介面上的改動都會引起客戶端程式碼的改動。

虛方法

TradeProcessor類的另一種實現包含了一個擴充套件點:ProcessTrades是個虛方法。

任何一個帶有虛方法成員的類都是對外開放的,這種擴充套件是通過繼承做到的。可以修改其子類的ProcessTrades方法而無需改變原有的TradeProcessor類原始碼。此時的TradeProcessorClient類也不需要做改動,可以使用多型向客戶端提供新版本的TradeProcessor2類的例項。

但是使用虛方法能重新實現的範圍是有一定限制的。在子類中可以訪問基類,因此可以直接呼叫TradeProcessor類的ProcessTrades方法,但是無法改動該方法內的任何程式碼。要麼在子類方法裡呼叫基類同名方法並在其前後實現新的特性,要麼完全重新實現子類的方法。虛方法沒有中間狀態。另外子類只能訪問基類的受保護和公共成員,如果基類中有很多子類無權訪問的私有成員,可能就需要修改基類的實現了。但是,這又會違背開放封閉原則。

mark

抽象方法

另外一種使用實現繼承的更加靈活的擴充套件點是抽象方法。

客戶端依賴抽象基類,因此提供任何一個具體子類(或者用來支援新需求的子類)給客戶端都不會違背開放封閉原則。

mark

介面繼承

最後一個擴充套件點是實現繼承外的另外一種選項:介面繼承。客戶端委託介面取代了客戶端對類的依賴。

介面繼承要比實現繼承好很多。基於實現繼承,所有子類(現有的和將來的)都是基類的客戶端。給繼承圖頂部節點新增新成員的改動會影響到該層級結構下的所有成員,而介面要比類靈活的多。這當然不是說代表實現繼承的虛方法和抽象方法提供的擴充套件點沒有一點用處,但是它們的確無法提供與介面一樣強大的自適應能力。

mark

防止變異

雖然我們已經知道了實現擴充套件點的方式,但是我們應該到處都留著擴充套件點嗎?防止變異是另外一個跟開放封閉原則相關的重要準則:

識別可預見的變化點並圍繞它們建立一個穩定的介面。

可預見的變化

要識別出很可能發生變更的需求或者實現起來特別麻煩的程式碼部分,然後將它們隱藏在擴充套件點之後。

一個穩定的介面

依賴介面的最大優勢是介面變化的可能性要比實現小很多。用於表達擴充套件點的所有介面應該都是穩定的。因為客戶端是直接依賴介面的,如果介面發生變化,客戶端也必須做相應的改動。

最後

通過確保程式碼對擴充套件開放對修改封閉,可以有效阻止後期變化對現有類的修改,因為後面的編碼人員只能在你預留的擴充套件點上掛靠新建立的類。程式碼可以很死板,幾乎無法擴充套件和細化;程式碼也可以很流暢,帶有足夠的準備應對新需求的大量擴充套件點。兩種選擇都沒有錯,只是要在具體的場景進行選擇和應用。

參考

《C#敏捷開發實踐》

作者: CoderFocus 微信公眾號: