0 設計模式基礎
0.0 設計模式的定義
先來看一下設計模式常見的書面定義:
- 設計模式是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。
- 設計模式是指在軟件開發中,經過驗證的,用於解決在特定環境下,重復出現的、特定問題的解決方案。
設計模式更多的是一種實際應用中經驗的基類和總結,並得到了多數人的認可和驗證,經過更規范的整理和分類及命名,成為了一種眾所周知的知識體系。
0.1 設計模式的分類
一般情況下說到的設計模式都是指Gof著作中所講述的23中經典設計模式。
-
創建型模式:單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式。
-
結構型模式:適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。
-
行為型模式:模版方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式、狀態模式、策略模式、職責鏈模式(責任鏈模式)、訪問者模式。
1 面向接口編程
上學的時候經常跟室友一起玩兒War3,我一直比較鍾情於暗夜精靈族,暗夜精靈有4個灰常厲害的英雄,每個英雄都會有4個技能。剛開始學習面向對象思想的時候,我已經知道“英雄”可以定義成一個類,假設每個英雄出場時候都要秀一下自己華麗的四個技能,我們在構造函數中傳遞英雄名稱,構造不同的英雄對象。
/// <summary> /// 英雄類 /// </summary> public class Hero { private readonly string _name; // 英雄名稱 /// <summary> /// 構造函數 /// </summary> /// <param name="name">英雄名稱</param> public Hero(string name) { _name = name; } /// <summary> /// 秀出自己的技能 /// </summary> public void ShowSkills() { switch (_name) { case "DH": Console.WriteLine("我是惡魔獵手,我會法力燃燒、獻祭、閃避和變身。"); break; case "WD": Console.WriteLine("我是守望者,我會暗影突襲、刀陣、閃爍和復仇天神。"); break; case "KOG": Console.WriteLine("我是叢林守護者,我會纏繞根須、荊棘光環、自然之力和寧靜。"); break; case "POM": Console.WriteLine("我是月亮女祭司,我會召喚貓頭鷹,灼熱之箭,強擊光環和流星雨。"); break; default: break; } } }
英雄輪流秀出的自己的技能(排名不分先后)
class Program { static void Main(string[] args) { Hero dh = new Hero("DH"); dh.ShowSkills(); Hero wd = new Hero("WD"); wd.ShowSkills(); Hero kog = new Hero("KOG"); kog.ShowSkills(); Hero pom = new Hero("POM"); pom.ShowSkills(); } }
輸出結果
上面的代碼存在的問題主要有2個:
- 如果某天我不再玩兒暗夜精靈了,改玩兒別的種族,Hero類中的ShowSkills方法就要完全修改一遍。
- 秀出自己的技能只是一個最基本的行為,英雄還具有攻擊、移動、釋放技能等行為,每個英雄攻擊力、移動速度、釋放技能耗藍卻又都各不相同。
這里就涉及到了面向接口編程的重要原則。
通常接口用來定義實現類的外觀,提取實現類共有的行為定義。接口類似於一種契約,根據外部應用的需要,約定了實現類應該實現的功能,而具體內部如何實現,應由具體的實現類控制,同時具體的實現類除了要實現接口規定的行為外,還可以根據需要實現自己獨有的行為,也就是說實現類的功能應包含但不僅限於接口定義的功能。
由於外部調用和內部實現被接口隔離開了,外部調用只通過接口調用,也就是說只要接口不變,內部具體實現的變化就不會對外部調用產生任何影響。這樣使用接口的好處就很明顯了,當我們增加其他英雄的時候只需要增加一個實現英雄接口的具體實現類既可,對原有已實現的不分不會造成任何影響。提現了接口“封裝隔離”的思想。
定義英雄接口:
/// <summary> /// 英雄接口定義 /// </summary> public interface IHero { /// <summary> /// 秀技能 /// </summary> void ShowSkills(); }
不同英雄的具體實現:
/// <summary> /// 惡魔獵手 /// </summary> public class DH : IHero { /// <summary> /// 秀出自己的技能 /// </summary> public void ShowSkills() { Console.WriteLine("我是惡魔獵手,我會法力燃燒、獻祭、閃避和變身。"); } }
/// <summary> /// 守望者 /// </summary> public class WD : IHero { /// <summary> /// 秀出自己的技能 /// </summary> public void ShowSkills() { Console.WriteLine("我是守望者,我會暗影突襲、刀陣、閃爍和復仇天神。"); } }
/// <summary> /// 叢林守護者 /// </summary> public class KOG : IHero { /// <summary> /// 秀出自己的技能 /// </summary> public void ShowSkills() { Console.WriteLine("我是叢林守護者,我會纏繞根須、荊棘光環、自然之力和寧靜。"); } }
/// <summary> /// 月亮女祭司 /// </summary> public class POM : IHero { /// <summary> /// 秀出自己的技能 /// </summary> public void ShowSkills() { Console.WriteLine("我是月亮女祭司,我會召喚貓頭鷹,灼熱之箭,強擊光環和流星雨。"); } }
修改外部調用部分,同樣每個英雄都正確的秀出了自己的技能。
IHero dh = new DH(); dh.ShowSkills(); IHero wd = new WD(); wd.ShowSkills(); IHero kog = new KOG(); kog.ShowSkills(); IHero pom = new POM(); pom.ShowSkills(); Console.ReadLine();
到這里我們就會發現了新的問題,上面說過接口的作用的就是封裝隔離,外部調用只知道接口的存在,不應該依賴於具體的實現類。而接口又是沒有辦法通過new關鍵字進行實現話的,如何解決這個矛盾?
2 簡單工廠模式
簡單工廠模式並不包含在上述23種經典設計模式之中,也有人說簡單工廠並不能算得上一個設計模式。不管怎么說,簡單工廠卻是實實在在的簡單易用,還記得當年還在學校的時候,由於沒有經過實際項目的歷練,在編碼經驗不足的情況下,去試着了解設計模式的時候,大部分設計模式是無法深入理解的,也就只有看到簡單工廠模式的時候會驚呼:“原來還可以這樣”抑或“原來我一直用的就是簡單工廠”。
簡單來說,簡單工廠就是通過定義一個工廠類,這個工廠類提供了一個創建具體實例的功能,外部調用只需要告訴工廠需要什么類型的實例,工廠負責創建這個實例,外部調用無需關心其具體實現,從而達到真正的接口隔離的目的。
簡單工廠類中創建具體實例的方法一般定義為靜態方法,從而可以避免在外部調用的時候再new簡單工廠的對象,因此簡單工廠模式一般也被成為靜態工廠模式。
我們定義一個簡單工廠類。
/// <summary> /// 簡單工廠類 /// </summary> public class Factory { /// <summary> /// 創建英雄的靜態方法 /// </summary> /// <param name="heroName">英雄名稱</param> /// <returns></returns> public static IHero CreateHero(string heroName) { switch (heroName) { case "DH": return new DH(); case "WD": return new WD(); case "KOG": return new KOG(); case "POM": return new POM(); default: return null; } } }
然后外部調用的時候通過簡單工廠方法創建各個英雄的實例。
IHero dh = Factory.CreateHero("DH"); dh.ShowSkills(); IHero wd = Factory.CreateHero("WD"); wd.ShowSkills(); IHero kog = Factory.CreateHero("KOG"); kog.ShowSkills(); IHero pom = Factory.CreateHero("POM"); pom.ShowSkills();
初識簡單工廠模式的時候,最大的疑問就是,這個簡單工廠類,無非就是把原來在外部調用時創建具體英雄類實例的代碼挪了過去,在簡單工廠類里面還是一樣需要通過new指定具體的類來進行接口的實例化啊,而且還白白多了一個簡單工廠類,意義何在?
其實簡單工廠類最大的意義還是起到接口隔離的作用,看到隔離這個詞,就肯定有隔離的雙方(封裝體),我們多增加一個簡單工廠類,表面上看是多了一個類,並沒有減少任何代碼,也沒有對代碼進行大的更改,只是一個英雄實例化代碼的位置移動,簡單工廠模式的精髓恰恰就是這個具體類實例化代碼位置的移動,我們知道,使用接口的目的就是不讓外部調用知道封裝體內部的實現,在使用簡單工廠類之前,我們創建英雄實例的代碼很明顯的是位於外部調用部分的,這樣其實就是沒有隔離,由於簡單工廠類位於封裝體內部的,工廠類是可以知道具體的實現細節的。使用簡單工廠類后,相當於這個封裝提對外只公開了一個IHero接口及一個工廠類創建英雄的方法給外部調用,這樣隔離就很明確了,只是一段代碼位置的移動,從設計上來講,已經發生了本質的變化。
3 簡單工廠的幾點建議
- 工廠方法靜化
簡單工廠類中創建實例的方法,應為靜態方法。
- 實例創建配置文件化
實例創建應盡量通過配置文件及反射機制,動態創建,達到能根據某個值,自動判斷並創建對應類的實例的目的,這樣就可以將龐大的swith語句塊消除,同時,實例化部分的修改,只需要修改配置文件即可。
- 簡單工廠模塊化
一個簡單工廠可以定義多個創建實例的靜態方法,建議按照不同的功能模塊,創建不同的工廠類。因為簡單工廠類是一個模塊封裝提的一部分。
4 簡單工廠模式的優點
- 封裝
能夠非常簡單快捷的實現模塊的組件化,組件通過對外公開接口,實現面向接口編程
- 解耦
實現了外部調用和具體實現的解耦,增強了系統的健壯性和易維護性。