一、前言
專案中使用 Design Pattern 越多越好? 哪種 Pattern 比較重要? 需要學習哪些 Pattern ?
對於入門者來說,直接上手學習 Design Pattern 可能會產生很多困惑,認為程式設計很難。好比修煉神功祕籍,首先得會基本功曉原理;其次才能進階修煉高階術法,除非有高人指點,否則輕則自廢武功重則走火入魔。
其實不必, 沒有 Design Pattern 也可以寫出 “Good Code”。應用程式首先是用來解決問題,不要過分關心 Design Pattern 沒有必要為了 Pattern 而 Pattern;但軟體開發領域 “Design Pattern” 又很重要,怎麼辦? 有個建議可以從 “SOLID、DRY” 開始,物件導向程式設計的基本原則。
物件導向原則 & Design Pattern 兩者區別:
物件導向基本原則:是一種指導規範,應該怎麼幹,告訴開發者你應該遵循這種規範。
Design Pattern :是遵循程式設計規範解決特定問題的具體實踐。
二、物件導向基本原則
SOLID 由美國軟體工程師 Robert C. Martin 總結提出 。維基百科 :https://en.wikipedia.org/wiki/SOLID 。
SOLID:
S - <Single-responsiblity Principle> 單一職責原則
O - <Open-closed Principle>開閉原則
L -<Liskov Substitution Principle>里氏替換原則
I - <Interface Segregation Principle>介面隔離原則
D -<Dependency Inversion Principle>依賴倒置原則
DRY: < Don't repeat yourself> 不要重複自己原則
1、Single-responsiblity Principle
單一職責描述:一個類只有一個用途,沒有兩個用途,沒有三個用途。
好比假設汽車的剎車、油門踏板沒有分開設計,被設計成 剎車油門踏板,或者把離合也加進去。想象一下,使用的時候垂直踏下去是剎車,向左偏向下踏是加油,向右向下踏是離合;這樣設計的後果 不僅考驗駕車者的車技,甚者交通事故率大概率會直線上升。
實踐中如果發現類規模越來越大職責越來越多,此時開始著手重構它。
實踐中如果發現一個方法,隨著業務變動引數越來越多、方法體越來越臃腫、乾的事情越來越多,最顯而易見的是 If else 語句變多,此時著手重構它。
題外:分析具體情況,引數多是不是也可以重構為使用一個物件傳遞,If else 語句多使用 “表驅動法” 、Factory Pattern 解決。
2、Open-closed Principle
開閉原則描述:軟體實體應該對擴充套件開放,對修改關閉。
現在各種計步手環特別火,公司安排你做一個整合平臺,展示跑步線路。該怎麼做?
假設你是一個軟體工程師,加班太多以至於沒有時間打掃房間,你僱傭了一個王阿姨打掃房間,一直相安無事,突然有一天王阿姨家有事回家了。剛好這一天心儀的女同事提出去你家做客,而你焦慮房間亂沒人打掃。 有了這次經驗,你可能會找一個家政公司幫你打掃房間,不用關心王阿姨請假或者有事。 你稱之為 “中介者原則”,依賴中介去打掃衛生,而不是依賴某個阿姨,阿姨會有事,中介公司不打樣。
實踐中我們依賴抽象不依賴實現,如果發現依賴實現,此時重構它。
實踐中如果寫 “Hello World” ,我不會考慮任何原則或者模式。
現在開始寫線路解析程式,遵循 “中介者原則” 定義一個“抽象解析中介” 然後依賴並實現它。 以後不管再來幾個手環廠家,我們都可以做擴充套件,而不用修改已有的解析程式。
3、Liskov Substitution Principle
里氏替換原則描述:子類或者派生類可以替代其基類或父類,用子類例項替代父類例項不產生負面影響。
最直觀的例子就是,你不能用黃種人作為有色人種或者白色人種的祖先,或者其他人種的祖先作為黃種人的祖先。
Code Show:
public class YellowRace { private string _color; public YellowRace() { _color = "Yellow"; } public virtual string GetColor() { return _color; } } public class WhiteRace : YellowRace { private string _color; public WhiteRace() { _color = "White"; } public override string GetColor() { return _color; } } static void Main(string[] args) { YellowRace yellowRace = new YellowRace(); WhiteRace whiteRace = new WhiteRace(); Console.WriteLine($"Yellow Race Skin Color Is: {yellowRace.GetColor()}"); // 此處使用子類替換父類,會產生負作用 , }
上面程式碼違反 LIP 原則,負面影響:即黃種人,皮膚顏色應是黃色。
可以重構,建立一個 Mankind 類, 白種人、黃種人都繼承它。
public abstract class Mankind { protected string _color; public abstract string GetColor(); } public class YellowRace: Mankind { public YellowRace() { _color = "Yellow"; } public override string GetColor() { return _color; } } public class WhiteRace : Mankind { public WhiteRace() { _color = "White"; } public override string GetColor() { return _color; } }
4、Interface Segregation Principle
介面隔離原則描述:多個客戶端特定的介面,好過一個超級通用介面,即客戶端不應該實現使用不到的介面,或客戶端不應該依賴於不使用的方法。
實踐中曾經閱讀過這樣的程式碼違反ISP 。 一個介面中有好多好多行為,其中一個行為在實現類中只有一個類真正用到了,其餘的方法體中都是 “throw new NotImplementedException();” ,此時應該著手重構它。
題外,使用抽象類還是介面 ? 介面通常作為行為規範,表示能幹什麼 Can Do;而抽象類表示是什麼 Is A 。例如:印表機能列印,則印表機應該實現一個列印介面,同時印表機是一個電器則可以繼承電器基類。
5、Dependency Inversion Principle
依賴倒置原則描述:應該依賴抽象,而不是具體實現。
現實中最長用的 ORM 框架,設計的時候遵循此原則,依賴抽象契約而不是某種特定資料庫的實現。
回到僱傭王阿姨打掃房間的例子,依賴中介公司,就是依賴抽象,所有就可以讓任何阿姨打掃房間。
6、DRY
不要重複你自己原則描述:相同的程式碼不應該存在兩份以上。
實踐中相同的程式碼同時存在多份,此時著手重構它。
三、總結
以上介紹了幾種程式設計原則,遵循這些原則,就可以寫出 “Good Code”。
適用於目前所有的面嚮物件語言,這些原則指導軟體開發,並積累了一些實踐。有助於軟體程式碼清晰可讀、可擴充套件、高內聚低耦合,有助於程式碼重構,清除程式碼異味。SOLID 被典型應用在,測試驅動開發、敏捷開發、自適應軟體開發等領域。