# 25. Design Principles - SOLID ###### tags: `DesignPatterns` ## 關於 Design Principle 本篇將討論以下幾個問題 > ### 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 --- ## 1. 關於 Design Principles 即便知道封裝、繼承、多型三大特性,也無法一下子就將程式寫得符合[物件導向設計(Object-Oriented Programming, OOP)](https://zh.wikipedia.org/wiki/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1),而設計原則可以協助我們以明確的定義作為標準,評斷程式中那些部分可以優化,以達到提高可讀性、維護性與擴充性的目的。 --- ## 2. 關於 SOLID SOLID 為設計原則中最廣為人知的五個原則,由大神 [Robert C. Martin](https://en.wikipedia.org/wiki/Robert_C._Martin) 在 2000 年的 paper 「[Design Principles and Design Patterns](http://www.cvc.uab.es/shared/teach/a21291/temes/object_oriented_design/materials_adicionals/principles_and_patterns.pdf)」中介紹的(並非全都由 Robert C. Martin 提出),中文版可以參考[無瑕的程式碼 敏捷完整篇](https://www.tenlong.com.tw/products/9789864342099?list_name=srh)。 #### SOLID 是由五個設計原則的字首~~(藏頭詩?)~~拼成,分別是 | 名稱(縮寫) | 中文 | | -------- | -------- | | **S**ingle-Responsibility Principle(SRP) | 單一職責原則 | | **O**pen-Closed Principle(OCP) | 開閉原則 | | **L**iskov Substitution Principle(LSP) | 里氏替換原則 | | **I**nterface Segregation Principles(ISP) | 介面隔離原則| | **D**ependency Inversion Principle(DIP) | 依賴反轉原則 | --- ## 3. Single-responsibility principle #### 單一職責原則,一個 class 或模組應該只有一個職責 不同的層面來看同一個 class 或模組的職責是否足夠單一會有不同的結果(e.g. 從整個購物流程的層面來看,包含**加入購物車** & **付款**很合理,但從結帳流程來看,**加入購物車**卻顯得格格不入),而我們還是可以從一些比較客觀的方式來判斷 - class 或模組本身很龐大(e.g. 屬性或方法數量很多) - class 或模組本身難以命名 - 依賴過多 --- ## 4. Open–closed principle #### 開閉原則,對擴展開放,對修改封閉 要達到 OCP 最簡單的例子就是 abstract class 讓子類別 override,但其它時候呢?對擴展開發聽起來很合理,但對修改封閉卻很抽象,什麼樣的程度算是修改?是否有異動到物件本身就算是違反 OCP 呢? 實務上很難完全不會異動到物件本身,所以個人比較偏向這個看法 - 對擴展開放是為了因應變化 - 對修改封閉是為了保持原有程式的穩定 在這個前提之下,儘管我們在物件中新增了屬性、方法,但沒有修改到原有的程式,因應變化並保持原有程式穩定性,故沒有違反 OCP --- ## 5. Liskov substitution principle #### 里氏替換原則,子類別繼承父類別,可以修改實作,但不能改變父類別原有的約定(e.g. 回傳型別、參數、Exception的處理等) 有個簡單的驗證方式,將父類別的單元測試套用於子類別上,能通過單元測試通常都符合 LSP。違反 LSP 時未必都是子類別不遵守合約,也可能是抽象類別或介面設計上的錯誤,我們來看看實際的例子 - (O) 父類別定義方法`GetStartDate()`,子類別實作並回傳**開始日期** - (X) 父類別定義方法`GetStartDate()`,子類別實作改為回傳**結束日期** - (O) 父類別哺乳類,定義方法`Walk()`,子類別**人類**實作走路 - (X) 父類別哺乳類,定義方法`Walk()`,子類別**鯨魚**無法實作走路 --- ## 6. Interface segregation principle #### 介面隔離原則,呼叫端不應該被強迫依賴它不需要的介面 ISP 跟 SRP 有點像,但角度有些不同,ISP 是由呼叫端的角度來判斷,SRP 則是關注在自己本身,而這邊說到的介面可以比較廣泛的解釋,像是 - interface 中有部分方法沒被呼叫端使用到,就應該拆分出來 - 呼叫端只需要一個`UserName`,但呼叫的方法 / API 卻回傳`User`所有欄位回來 --- ## 7. Dependency inversion principle #### 依賴反轉原則 > 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 在說什麼,透過原文的說明可以知道,高階模組在呼叫低階模組時,應透過抽象,而不是直接呼叫低階模組的實作,藉此降低高低階模組間的耦合。 1. 高階模組`DataTask` & 低階模組`LogService`皆依賴 interface `ILogService` 2. 低階模組`LogService`就算沒有 interface 也不會反過來依賴高階模組`DataTask` 3. 高階模組`DataTask`與低階模組`LogService`之間為鬆耦合 ```C# public interface ILogService { void Info(DateTime time, StatusType status); } public class LogService : ILogService { public void Info(DateTime time, StatusType status) { // 實作寫 log } } public class DataTask { private ILogService _log { get; } public DataTask(ILogService logService) { _log = logService; } public void 處理資料排程() { _log.Info(DateTime.Now, StatusType.Start); // 處理資料實作 _log.Info(DateTime.Now, StatusType.End); } } ``` --- ## 總結 ### Design Principles 可能會因為從不同的角度(層面)而有不同的解讀,而原則是死的,很多時候並非絲毫不差的遵守原則就是好的設計,過度從更細的層面來檢視是否符合 Design Principles 可能會造成過度設計且讓整體程式變得非常複雜,但什麼才是剛好的設計就只能從經驗來拿捏了。 --- ## 參考資料 1. [Wikipedia](https://en.wikipedia.org/wiki/SOLID) 2. [無瑕的程式碼 敏捷完整篇](https://www.tenlong.com.tw/products/9789864342099?list_name=srh) --- ## 新手上路,若有錯誤還請告知,謝謝
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up