開放與封閉原則有兩種不一樣的定義,分別是20世紀80年代最原始的定義和後期一個更現代的定義,後者對前者進行更加詳盡的闡述。微信
軟件實體應該容許擴展,但禁止修改post
——《面向對象軟件構造》學習
」對於擴展是開放的。「 這意味着模塊的行爲是能夠擴展的。當應用程序的需求改變時,咱們能夠對其模塊進行擴展,使其具備知足那些需求變動的新行爲。換句話說,咱們能夠改變模塊的功能。編碼
「對於修改是封閉的。「 對模塊行爲進行擴展時,沒必要改動該模塊的源代碼或二進制代碼。模塊的二進制可執行版本,不管是可連接的庫、DLL或Java的.jar文件,都無需改動。spa
——《敏捷軟件開發:原則、模式與實踐》對象
須要注意的是,「對於修改是封閉的」有兩個例外:blog
1.修復缺陷所作的改動繼承
2.客戶端沒法感知到的改動接口
缺陷在軟件中很常見,是不可能徹底消除的。當缺陷出現時,就須要咱們修復現有的代碼。軟件修復明顯傾向於實用主義而不是堅持開放封閉原則。開發
若是一個類的改動會引發另外一個類的改動,那麼這兩個類就是緊密耦合的。相反,若是一個類的修改老是獨立的,並不會引發其餘類的改動,那麼這些類就是鬆散耦合的。咱們要記住,任何狀況下,鬆散耦合都比緊密耦合要好。若是咱們對現有代碼的修改不會影響客戶端代碼,那麼也就談不上違背開放封閉原則。
TradeProcessorClient類直接依賴TradeProcessor類。當接到一個須要改動TradeProcessor類的新需求時,爲了避免改變原有的類型,建立了一個新類型(TradeProcessor2)來實現需求提出的新功能。可是這種改動帶來的反作用就是必須改動TradeProcessorClient類,這樣才能依賴的新的TradeProcessor2類。
若是對現有代碼的改動不會影響客戶端,那就不須要建立新類型。可是若是對現有代碼的改動改變了TradeProcessor類方法的簽名,那就不是簡單的對類實現的改動,而是對接口的改動了。由於客戶端老是與服務的接口緊密耦合的,因此任何接口上的改動都會引發客戶端代碼的改動。
TradeProcessor類的另外一種實現包含了一個擴展點:ProcessTrades是個虛方法。
任何一個帶有虛方法成員的類都是對外開放的,這種擴展是經過繼承作到的。能夠修改其子類的ProcessTrades方法而無需改變原有的TradeProcessor類源碼。此時的TradeProcessorClient類也不須要作改動,可使用多態向客戶端提供新版本的TradeProcessor2類的實例。
可是使用虛方法能從新實現的範圍是有必定限制的。在子類中能夠訪問基類,所以能夠直接調用TradeProcessor類的ProcessTrades方法,可是沒法改動該方法內的任何代碼。要麼在子類方法裏調用基類同名方法並在其先後實現新的特性,要麼徹底從新實現子類的方法。虛方法沒有中間狀態。另外子類只能訪問基類的受保護和公共成員,若是基類中有不少子類無權訪問的私有成員,可能就須要修改基類的實現了。可是,這又會違背開放封閉原則。
另一種使用實現繼承的更加靈活的擴展點是抽象方法。
客戶端依賴抽象基類,所以提供任何一個具體子類(或者用來支持新需求的子類)給客戶端都不會違背開放封閉原則。
最後一個擴展點是實現繼承外的另一種選項:接口繼承。客戶端委託接口取代了客戶端對類的依賴。
接口繼承要比實現繼承好不少。基於實現繼承,全部子類(現有的和未來的)都是基類的客戶端。給繼承圖頂部節點添加新成員的改動會影響到該層級結構下的全部成員,而接口要比類靈活的多。這固然不是說表明實現繼承的虛方法和抽象方法提供的擴展點沒有一點用處,可是它們的確沒法提供與接口同樣強大的自適應能力。
雖然咱們已經知道了實現擴展點的方式,可是咱們應該處處都留着擴展點嗎?防止變異是另一個跟開放封閉原則相關的重要準則:
識別可預見的變化點並圍繞它們建立一個穩定的接口。
要識別出極可能發生變動的需求或者實現起來特別麻煩的代碼部分,而後將它們隱藏在擴展點以後。
依賴接口的最大優點是接口變化的可能性要比實現小不少。用於表達擴展點的全部接口應該都是穩定的。由於客戶端是直接依賴接口的,若是接口發生變化,客戶端也必須作相應的改動。
經過確保代碼對擴展開放對修改封閉,能夠有效阻止後期變化對現有類的修改,由於後面的編碼人員只能在你預留的擴展點上掛靠新建立的類。代碼能夠很死板,幾乎沒法擴展和細化;代碼也能夠很流暢,帶有足夠的準備應對新需求的大量擴展點。兩種選擇都沒有錯,只是要在具體的場景進行選擇和應用。
《C#敏捷開發實踐》