軟體實體(類別,模組,函式等等) 應該是可以擴展的,但不能被修改
概述
- 對於擴展是開放的 — 當需求變更時模組行為可以新增的
- 對於修改是封閉的 — 當進行擴展時,不需修改既有的程式碼
講白一點
在設計已經完整的前提下只能加Code不能改
例如Chrome瀏覽器在新增擴充工具時,不會叫你重新安裝Chrome吧
怎樣情況下會違反
當專案新增需求時,在判斷類別上面多加一個新增的物件,判斷完後在去做對應的事情,這樣做在設計上是直覺的,但是違反開放封閉原則
因為只要修改舊有的程式碼由於高耦合就有可能會因為脆弱性導致其他部分產生意料之外的BUG,尤其在不段增長的需求下,越是頻繁的改動,越容易凸顯問題
你是否有遇到?
- 為什麼別人改他的code,我這邊會爆掉?
高耦合與未作抽象隔離導致
- 要用 If else / switch 去判斷子類別然後個別做不同事情
代表在同一個class中處理不同情況,可能會導致相依也違反單一職責SRP
有什麼優點?
- 降低耦合
- 增加擴展性
- 增加可測試性
- 提身維護性
我該怎做?
怎樣才能做到”不變動模組又改變行為”呢?
答案是 “抽象”, 可以使用以下方法
- 利用繼承(多型)
- 利用抽象介面
- 依賴倒轉 (Dependency Injection pattern, DIP)
- 裝飾者模式
- 策略模式
下面有個計算面積的範例,利用抽象來達到OCP開放封閉原則
範例
AreaCalculator 拿來算總面積,會根據形狀來判斷計算的方法
這個看似沒什麼問題,但新增一種形狀就要修改AreaCalculator的totalArea方法,這違反了OCP 封閉修改
首先找出實作不一樣的地方進行抽象
所以需要將計算面積的行為抽象出來
再來宣告ShapeArea的 Protocol
要使用的形狀必須實作area這個計算面積的方法
計算時候因為每個Shape都會實作Area,所以加總起來即可
以上的範例
將計算面積這個行為抽象出來 開放擴展
下次要新增的時候也不會修改到AreaCalculator 封閉修改
總結
開放封閉在大型或複雜專案上是非常有感覺的,只要是相同類型對處理方式不同的,就可以思考是否遵守OCP
實際情況下當判斷的case只有一兩個時可以不用刻意去解耦合,因為設計上可能還沒完全,或者提早優化,如果利用經驗來做提前預測,大多情況下會預測失誤,畢竟遵循OCP的成本很高也會增加不必要的複雜
在書中提到,可以一直等到變化發生,也就是被搞過一次,這時候在進行隔離
一但隔離開開來,之後有BUG也可以快速找到問題,對於問題的修改不用擔心去連動到其他意料之外的地方,就會減少大量技術債
OCP跟DIP都是討論抽象概念,可以利用DIP方法來達成降低耦合進而遵循OCP,如果利用多型方法來實現,則需要遵守LSP,可以保證子類別的可替換性,來達到無需修改就可以擴展
不過個人認為利用多型的話,像是Objective-C 就沒有抽象類別,這樣不能保證子類別有去替換實作,所以可能會造成意料之外的結果
參考來源
Agile Principles, Patterns, and Practices in C#, Robert C. Martin (Author)