開放-封閉原則:Software Entities Should Be Open For Extension, Yet Closed For Modification
http://www.textcn.com/jishuwz/prosj/netnet/200612/55186.html
大家可能都有這樣的體會,要滿足各種各樣的客戶,并且客戶的需求經常變化,程序員就是這樣的辛苦命,整天改過來改過去,特別用一些非面向對象的語言寫的代碼,函數的參數變得越來越長,里面的Case情況慢慢增加,函數變得很大。軟件幾年之后就變得難于理解和維護,軟件的生命周期好像就要終止。如果能夠擴展模塊的功能,同時又不修改原來已經測試通過的代碼,那多好啊!
這完全是可以實現的,關鍵是抽象。把可能的變化用抽象來隔離它。面向接口編程,而不是面向對象編程,能增強程序的靈活性;如Client類調用Server 類,如果我們希望Client對象使用另外一個不通的Server對象,就必須修改Client中使用Server類的地方;如果Client調用 Server的接口就可以避免這種修改,只要生成新的接口實現類,修改Main等初次使用新子類的地方而不需要修改Client類;使用Strategy 模式和Template Method模式是滿足OCP的最常用方法。
如果需要適應某種變化就需要對這種變化進行抽象,會增加程序的復雜度。所以設計人員應該熟悉業務和了解客戶需求,預測到需要進行抽象的變化。
敏捷建模不建議提前進行各種假想的變化抽象,而是當變化發生第一次的時候抽象這種變化,以后同樣的變化就變得很容易。對代碼進行重構以保持良好的結構是很重要的,每次抽象都不應該使軟件變得越來越僵化。這是非面向對象的語言不具備的優勢。
參考:Health King的專欄
滿足OCP的設計給系統帶來兩個無可比擬的優越性.
- 通過擴展已有的軟件系統,可以提供新的行為,以滿足對軟件的新需求,使變化中的軟件系統有一定的適應性和靈活性.
- 已有的軟件模塊,特別是最重要的抽象層模塊不能再修改,這就使變化中的軟件系統有一定的穩定性和延續性.
具有這兩個優點的軟件系統是一個高層次上實現了復用的系統,也是一個易于維護的系統.那么,我們如何才能做到這個原則呢?不能修改而可以擴展,這個看起來是自相矛盾的.其實這個是可以做到的,按面向對象的說法,這個就是不允許更改系統的抽象層,而允許擴展的是系統的實現層.
解決問題的關鍵在:抽象化.我們讓模塊依賴于一個固定的抽象體,這樣它就是不可以修改的;同時,通過這個抽象體派生,我們就可以擴展此模塊的行為功能.如此,這樣設計的程序只通過增加代碼來變化而不是通過更改現有代碼來變化,前面提到的修改的副作用就沒有了.
"開-閉"原則如果從另外一個角度講述,就是所謂的"對可變性封裝原則"(Principle of Encapsulation of Variation, EVP).講的是找到一個系統的可變因素,將之封裝起來.在我們考慮一個系統的時候,我們不要把關注的焦點放在什么會導致設計發生變化上,而是考慮允許什么發生變化而不讓這一變化導致重新設計.也就是說,我們要積極的面對變化,積極的包容變化,而不是逃避.
[SHALL01]將這一思想用一句話總結為:"找到一個系統的可變因素,將它封裝起來",并將它命名為"對可變性的封裝原則".
"對可變性的封裝原則"意味者兩點:
- 一種可變性應當被封裝到一個對象里面,而不應當散落到代碼的很多角落里面.同一種可變性的不同表象意味著同一個繼承等級結構中的具體子類.繼承應當被看做是封裝變化的方法,而不應當是被認為從一般的對象生成特殊的對象的方法(繼承經常被濫用).
- 一種可變性不應當與另外一種可變性混合在一起.從具體的類圖來看,如果繼承結構超過了兩層,那么就意味著將兩種不同的可變性混合在了一起.
"對可變性的封裝原則"從工程的角度說明了如何實現OCP.如果按照這個原則來設計,那么系統就應當是遵守OCP的.
但是現實往往是殘酷的,我們不可能100%的遵守OCP,但是我們要向這個目標來靠近.設計者要對設計的模塊對何種變化封閉做出選擇.