DesignPatterns
1. 關於 Design Principles
2. 關於 SOLID
3. Single-responsibility principle
4. Open–closed principle
5. Liskov substitution principle
6. Interface segregation principle
7. Dependency inversion principle
即便知道封裝、繼承、多型三大特性,也無法一下子就將程式寫得符合物件導向設計(Object-Oriented Programming, OOP),而設計原則可以協助我們以明確的定義作為標準,評斷程式中那些部分可以優化,以達到提高可讀性、維護性與擴充性的目的。
SOLID 為設計原則中最廣為人知的五個原則,由大神 Robert C. Martin 在 2000 年的 paper 「Design Principles and Design Patterns」中介紹的(並非全都由 Robert C. Martin 提出),中文版可以參考無瑕的程式碼 敏捷完整篇。
名稱(縮寫) | 中文 |
---|---|
Single-Responsibility Principle(SRP) | 單一職責原則 |
Open-Closed Principle(OCP) | 開閉原則 |
Liskov Substitution Principle(LSP) | 里氏替換原則 |
Interface Segregation Principles(ISP) | 介面隔離原則 |
Dependency Inversion Principle(DIP) | 依賴反轉原則 |
不同的層面來看同一個 class 或模組的職責是否足夠單一會有不同的結果(e.g. 從整個購物流程的層面來看,包含加入購物車 & 付款很合理,但從結帳流程來看,加入購物車卻顯得格格不入),而我們還是可以從一些比較客觀的方式來判斷
要達到 OCP 最簡單的例子就是 abstract class 讓子類別 override,但其它時候呢?對擴展開發聽起來很合理,但對修改封閉卻很抽象,什麼樣的程度算是修改?是否有異動到物件本身就算是違反 OCP 呢?
實務上很難完全不會異動到物件本身,所以個人比較偏向這個看法
在這個前提之下,儘管我們在物件中新增了屬性、方法,但沒有修改到原有的程式,因應變化並保持原有程式穩定性,故沒有違反 OCP
有個簡單的驗證方式,將父類別的單元測試套用於子類別上,能通過單元測試通常都符合 LSP。違反 LSP 時未必都是子類別不遵守合約,也可能是抽象類別或介面設計上的錯誤,我們來看看實際的例子
GetStartDate()
,子類別實作並回傳開始日期GetStartDate()
,子類別實作改為回傳結束日期Walk()
,子類別人類實作走路Walk()
,子類別鯨魚無法實作走路ISP 跟 SRP 有點像,但角度有些不同,ISP 是由呼叫端的角度來判斷,SRP 則是關注在自己本身,而這邊說到的介面可以比較廣泛的解釋,像是
UserName
,但呼叫的方法 / API 卻回傳User
所有欄位回來High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.
單看命名滿難理解 DIP 在說什麼,透過原文的說明可以知道,高階模組在呼叫低階模組時,應透過抽象,而不是直接呼叫低階模組的實作,藉此降低高低階模組間的耦合。
DataTask
& 低階模組LogService
皆依賴 interface ILogService
LogService
就算沒有 interface 也不會反過來依賴高階模組DataTask
DataTask
與低階模組LogService
之間為鬆耦合