一、引言
C#版本的23種設計模式已經寫完了,現在也到了一個該總結的時候了。說起設計模式,我的話就比較多了。剛開始寫程式碼的時候,有需求就寫程式碼來解決需求,如果有新的需求,或者需求變了,我就想當然的修改自己的程式碼來滿足新的需求,這樣做感覺是理所當然的,也沒感覺有什麼不妥的地方。寫了兩年多程式碼,偶爾一次,聽說了設計模式,據聽說設計模式就是軟體界的“獨孤九劍”,學會之後就可以天下無敵,於是我就開始了我的進修之路。
想當初,我就像很多的初學程式設計的人一樣,在面試官面前很容易說出面嚮物件語言的三大特徵:繼承,多型和封裝,其實,我根本不理解這三個詞語的意思,或者說理解的很淺薄。當然也有很多概念是不清楚的,比如:OOPL(面嚮物件語言)是不是就是OO的全部,物件導向的設計模式和設計模式的到底有什麼區別,等等相關問題。自從自己學了設計模式,很多概念變得清晰明瞭,做事有原則了,或者說有準則了,不像以前只是解決了問題就好,根本不管程式碼寫的是否合理。寫的程式碼可以很優美了,結構更合理了,自己開始重新思考程式碼這個東西。
學習設計模式並不是一帆風順的,尤其是剛開始的時候,由於自己很多概念不是很清楚,也走了很多彎路,但是通過自己的堅持,通過自己不斷的看,不斷的寫,對模式的理解也越來越準確了。寫程式碼不是一個很簡單的事情,做任何事都是有原則的,寫程式碼也一樣,如果自己心中對做的事情沒有準則,那就和無頭的蒼蠅一樣,做與不做是一樣的。寫程式碼和寫好程式碼是不一樣的,如果要寫好的程式碼,考慮的問題更多了,考慮穩定性,擴充套件性和耦合性,當然也要考慮上游和下游關聯的問題,讓你做事的時候知道怎麼做,也知道為什麼這麼做,這就是學習設計模式帶來的好處。
好了,說了也不少了,我把我寫的模式都羅列出來,每個模式都有連結,可以直接點選進入檢視和閱讀,希望對大家閱讀有益。
系列導航:
建立型:
C#設計模式(1)——單例模式(Singleton Pattern)
C#設計模式(2)——工廠方法模式(Factory Pattern)
C#設計模式(3)——抽象工廠模式(Abstract Pattern)
C#設計模式(4)——建造模式(Builder Pattern)
C#設計模式(5)——原型模式(Prototype Pattern)
結構型:
C#設計模式(6)——介面卡模式(Adapter Pattern)
C#設計模式(7)——橋接模式(Bridge Pattern)
C#設計模式(8)——裝飾模式(Decorator Pattern)
C#設計模式(9)——組合模式(Composite Pattern)
C#設計模式(10)——外觀模式(Facade Pattern)
C#設計模式(11)——享元模式(Flyweight Pattern)
C#設計模式(12)——代理模式(Proxy Pattern)
行為型:
C#設計模式(13)——模板方法模式(Template Method)
C#設計模式(14)——命令模式(Command Pattern)
C#設計模式(15)——迭代器模式(Iterator Pattern)
C#設計模式(16)——觀察者模式(Observer Pattern)
C#設計模式(17)——中介者模式(Mediator Pattern)
C#設計模式(18)——狀態模式(State Pattern)
C#設計模式(19)——策略模式(Stragety Pattern)
C#設計模式(20)——責任鏈模式(Chain of Responsibility Pattern)
C#設計模式(21)——訪問者模式(Vistor Pattern)
C#設計模式(22)——備忘錄模式(Memento Pattern)
C#設計模式(23)——直譯器模式(Interpreter Pattern)
二、 物件導向的設計原則
寫程式碼也是有原則的,我們之所以使用設計模式,主要是為了適應變化,提高程式碼複用率,使軟體更具有可維護性和可擴充套件性。如果我們能更好的理解這些設計原則,對我們理解物件導向的設計模式也是有幫助的,因為這些模式的產生是基於這些原則的。這些規則是:單一職責原則(SRP)、開放封閉原則(OCP)、里氏代替原則(LSP)、依賴倒置原則(DIP)、介面隔離原則(ISP)、合成複用原則(CRP)和迪米特原則(LoD)。下面我們就分別介紹這幾種設計原則。
2.1、單一職責原則(SRP):
(1)、SRP(Single Responsibilities Principle)的定義:就一個類而言,應該僅有一個引起它變化的原因。簡而言之,就是功能要單一。
(2)、如果一個類承擔的職責過多,就等於把這些職責耦合在一起,一個職責的變化可能會削弱或者抑制這個類完成其它職責的能力。這種耦合會導致脆弱的設計,當變化發生時,設計會遭受到意想不到的破壞。(敏捷軟體開發)
(3)、軟體設計真正要做的許多內容,就是發現職責並把那些職責相互分離。
小結:單一職責原則(SRP)可以看做是低耦合、高內聚在物件導向原則上的引申,將職責定義為引起變化的原因,以提高內聚性來減少引起變化的原因。責任過多,引起它變化的原因就越多,這樣就會導致職責依賴,大大損傷其內聚性和耦合度。
2.2、開放關閉原則(OCP)
(1)、OCP(Open-Close Principle)的定義:就是說軟體實體(類,方法等等)應該可以擴充套件(擴充套件可以理解為增加),但是不能在原來的方法或者類上修改,也可以這樣說,對增加程式碼開放,對修改程式碼關閉。
(2)、OCP的兩個特徵: 對於擴充套件(增加)是開放的,因為它不影響原來的,這是新增加的。對於修改是封閉的,如果總是修改,邏輯會越來越複雜。
小結:開放封閉原則(OCP)是物件導向設計的核心思想。遵循這個原則可以為我們物件導向的設計帶來巨大的好處:可維護(維護成本小,做管理簡單,影響最小)、可擴充套件(有新需求,增加就好)、可複用(不耦合,可以使用以前程式碼)、靈活性好(維護方便、簡單)。開發人員應該僅對程式中出現頻繁變化的那些部分做出抽象,但是不能過激,對應用程式中的每個部分都刻意地進行抽象同樣也不是一個好主意。拒絕不成熟的抽象和抽象本身一樣重要。
2.3、里氏代替原則(LSP)
(1)、LSP(Liskov Substitution Principle)的定義:子型別必須能夠替換掉它們的父型別。更直白的說,LSP是實現面向介面程式設計的基礎。
小結:任何基類可以出現的地方,子類一定可以出現,所以我們可以實現面向介面程式設計。 LSP是繼承複用的基石,只有當子類可以替換掉基類,軟體的功能不受到影響時,基類才能真正被複用,而子類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。
2.4、依賴倒置原則(DIP)
(1)、DIP(Dependence Inversion Principle)的定義:抽象不應該依賴細節,細節應該依賴於抽象。簡單說就是,我們要針對介面程式設計,而不要針對實現程式設計。
(2)、高層模組不應該依賴低層模組,兩個都應該依賴抽象,因為抽象是穩定的。抽象不應該依賴具體(細節),具體(細節)應該依賴抽象。
小結:依賴倒置原則其實可以說是物件導向設計的標誌,如果在我們編碼的時候考慮的是面向介面程式設計,而不是簡單的功能實現,體現了抽象的穩定性,只有這樣才符合物件導向的設計。
2.5 介面隔離原則(ISP)
(1)、介面隔離原則(Interface Segregation Principle, ISP)指的是使用多個專門的介面比使用單一的總介面要好。也就是說不要讓一個單一的介面承擔過多的職責,而應把每個職責分離到多個專門的介面中,進行介面分離。過於臃腫的介面是對介面的一種汙染。
(2)、使用多個專門的介面比使用單一的總介面要好。
(3)、一個類對另外一個類的依賴性應當是建立在最小的介面上的。
(4)、一個介面代表一個角色,不應當將不同的角色都交給一個介面。沒有關係的介面合併在一起,形成一個臃腫的大介面,這是對角色和介面的汙染。
(5)、“不應該強迫客戶依賴於它們不用的方法。介面屬於客戶,不屬於它所在的類層次結構。”這個說得很明白了,再通俗點說,不要強迫客戶使用它們不用的方法,如果強迫使用者使用它們不使用的方法,那麼這些客戶就會面臨由於這些不使用的方法的改變所帶來的改變。
小結:介面隔離原則(ISP)告訴我們,在做介面設計的時候,要儘量設計的介面功能單一,功能單一,使它變化的因素就少,這樣就更穩定,其實這體現了高內聚,低耦合的原則,這樣做也避免介面的汙染。
2.6 組合複用原則(CRP)
(1)、組合複用原則(Composite Reuse Principle, CRP)就是在一個新的物件裡面使用一些已有的物件,使之成為新物件的一部分。新物件通過向這些物件的委派達到複用已用功能的目的。簡單地說,就是要儘量使用合成/聚合,儘量不要使用繼承。
(2)、要使用好組合複用原則,首先需要區分”Has—A”和“Is—A”的關係。 “Is—A”是指一個類是另一個類的“一種”,是屬於的關係,而“Has—A”則不同,它表示某一個角色具有某一項責任。導致錯誤的使用繼承而不是聚合的常見的原因是錯誤地把“Has—A”當成“Is—A”.例如:雞是動物,這就是“Is-A”的表現,某人有一個手槍,People型別裡面包含一個Gun型別,這就是“Has-A”的表現。
小結:組合/聚合複用原則可以使系統更加靈活,類與類之間的耦合度降低,一個類的變化對其他類造成的影響相對較少,因此一般首選使用組合/聚合來實現複用;其次才考慮繼承,在使用繼承時,需要嚴格遵循里氏替換原則,有效使用繼承會有助於對問題的理解,降低複雜度,而濫用繼承反而會增加系統構建和維護的難度以及系統的複雜度,因此需要慎重使用繼承複用。
2.7 迪米特法則(Law of Demeter)
(1)、迪米特法則(Law of Demeter,LoD)又叫最少知識原則(Least Knowledge Principle,LKP),指的是一個物件應當對其他物件有儘可能少的瞭解。也就是說,一個模組或物件應儘量少的與其他實體之間發生相互作用,使得系統功能模組相對獨立,這樣當一個模組修改時,影響的模組就會越少,擴充套件起來更加容易。
(2)、關於迪米特法則其他的一些表述有:只與你直接的朋友們通訊;不要跟“陌生人”說話。
(3)、外觀模式(Facade Pattern)和中介者模式(Mediator Pattern)就使用了迪米特法則。
小結:迪米特法則的初衷是降低類之間的耦合,實現型別之間的高內聚,低耦合,這樣可以解耦。但是凡事都有度,過分的使用迪米特原則,會產生大量這樣的中介和傳遞類,導致系統複雜度變大。所以在採用迪米特法則時要反覆權衡,既做到結構清晰,又要高內聚低耦合。
三、建立型模式
建立型模式就是用來解決物件例項化和使用的客戶端耦合的模式,可以讓客戶端和物件例項化都獨立變化,做到相互不影響。建立型模式包括單例模式、工廠方法模式、抽象工廠模式、建造者模式和原型模式。
3.1、單例模式(Singleton Pattern):解決的是例項化物件的個數的問題,該模式是把物件的數量控制為一個,該模式可以擴充套件,可以把例項物件擴充套件為N個物件,N>=2。比如物件池的實現。
動機(Motivate):在軟體系統構建的過程中,經常有這樣一些特殊的類,必須保證它們在系統中只存在一個例項,才能確保它們的邏輯正確性、以及良好的效率。如何繞過常規的構造器,提供一種機制來保證一個類只有一個例項?如果指望使用者不使用構造器來重複建立物件,這是不對的。這應該是類設計者的責任,而不是使用者的責任。
意圖(Intent):保證一個類僅有一個例項,並提供一個該例項的全域性訪問點。
具體結構圖如下所示:
示例程式碼:
1 /// <summary> 2 /// 單例模式的實現 3 /// </summary> 4 public sealed class Singleton 5 { 6 // 定義一個靜態變數來儲存類的例項 7 private static volatile Singleton uniqueInstance; 8 9 // 定義一個標識確保執行緒同步 10 private static readonly object locker = new object(); 11 12 // 定義私有建構函式,使外界不能建立該類例項 13 private Singleton() 14 { 15 } 16 17 /// <summary> 18 /// 定義公有方法提供一個全域性訪問點,同時你也可以定義公有屬性來提供全域性訪問點 19 /// </summary> 20 /// <returns></returns> 21 public static Singleton GetInstance() 22 { 23 // 當第一個執行緒執行到這裡時,此時會對locker物件 "加鎖", 24 // 當第二個執行緒執行該方法時,首先檢測到locker物件為"加鎖"狀態,該執行緒就會掛起等待第一個執行緒解鎖 25 // lock語句執行完之後(即執行緒執行完之後)會對該物件"解鎖" 26 // 雙重鎖定只需要一句判斷就可以了 27 if (uniqueInstance == null) 28 { 29 lock (locker) 30 { 31 // 如果類的例項不存在則建立,否則直接返回 32 if (uniqueInstance == null) 33 { 34 uniqueInstance = new Singleton(); 35 } 36 } 37 } 38 return uniqueInstance; 39 } 40 }
3.2、工廠方法模式(Factory Method Pattern):一種工廠生產一種產品,工廠類和產品類是一一對應的,他們是平行的等級結構,強調的是“單個物件”的變化。
動機(Motivate):在軟體系統的構建過程中,經常面臨著“某個物件”的建立工作:由於需求的變化,這個物件(的具體實現)經常面臨著劇烈的變化,但是它卻擁有比較穩定的介面。如何應對這種變化?如何提供一種“封裝機制”來隔離出“這個易變物件”的變化,從而保持系統中“其他依賴物件的物件”不隨著需求改變而改變?
意圖(Intent):定義一個建立物件的工廠介面,由其子類決定要例項化的類,將實際建立工作推遲到子類中。
具體結構圖如下所示:
3.3、抽象工廠模式(Abstract Factory Pattern):該模式關注的是多批多系列相互依賴的產品的變化,比如:SQLConnection,SQLCommand,SqlDataReader,SqlDataAdapter,就是一批相互依賴的物件,他們變化可以產生OledbConnection,OledbCommand,OledbDataReader,OledbDataAdapter
動機(Motivate):在軟體系統中,經常面臨著"一系統相互依賴的物件"的建立工作:同時,由於需求的變化,往往存在更多系列物件的建立工作。如何應對這種變化?如何繞過常規的物件建立方法(new),提供一種"封裝機制"來避免客戶程式和這種"多系列具體物件建立工作"的緊耦合?
意圖(Intent):提供一個建立一系列相關或相互依賴物件的介面,而無需指定它們具體的類。
具體結構圖如下所示:
程式碼例項:
1 /// <summary> 2 /// 下面以不同系列房屋的建造為例子演示抽象工廠模式 3 /// 因為每個人的喜好不一樣,我喜歡歐式的,我弟弟就喜歡現代的 4 /// 客戶端呼叫 5 /// </summary> 6 class Client 7 { 8 static void Main(string[] args) 9 { 10 // 哥哥的歐式風格的房子 11 AbstractFactory europeanFactory= new EuropeanFactory(); 12 europeanFactory.CreateRoof().Create(); 13 europeanFactory.CreateFloor().Create(); 14 europeanFactory.CreateWindow().Create(); 15 europeanFactory.CreateDoor().Create(); 16 17 18 //弟弟的現代風格的房子 19 AbstractFactory modernizationFactory = new ModernizationFactory(); 20 modernizationFactory.CreateRoof().Create(); 21 modernizationFactory.CreateFloor().Create(); 22 modernizationFactory.CreateWindow().Create(); 23 modernizationFactory.CreateDoor().Create(); 24 Console.Read(); 25 } 26 } 27 28 /// <summary> 29 /// 抽象工廠類,提供建立不同型別房子的介面 30 /// </summary> 31 public abstract class AbstractFactory 32 { 33 // 抽象工廠提供建立一系列產品的介面,這裡作為例子,只給出了房頂、地板、窗戶和房門建立介面 34 public abstract Roof CreateRoof(); 35 public abstract Floor CreateFloor(); 36 public abstract Window CreateWindow(); 37 public abstract Door CreateDoor(); 38 } 39 40 /// <summary> 41 /// 歐式風格房子的工廠,負責建立歐式風格的房子 42 /// </summary> 43 public class EuropeanFactory : AbstractFactory 44 { 45 // 製作歐式房頂 46 public override Roof CreateRoof() 47 { 48 return new EuropeanRoof(); 49 } 50 51 // 製作歐式地板 52 public override Floor CreateFloor() 53 { 54 return new EuropeanFloor(); 55 } 56 57 // 製作歐式窗戶 58 public override Window CreateWindow() 59 { 60 return new EuropeanWindow(); 61 } 62 63 // 製作歐式房門 64 public override Door CreateDoor() 65 { 66 return new EuropeanDoor(); 67 } 68 } 69 70 /// <summary> 71 /// 現在風格房子的工廠,負責建立現代風格的房子 72 /// </summary> 73 public class ModernizationFactory : AbstractFactory 74 { 75 // 製作現代房頂 76 public override Roof CreateRoof() 77 { 78 return new ModernizationRoof(); 79 } 80 81 // 製作現代地板 82 public override Floor CreateFloor() 83 { 84 return new ModernizationFloor(); 85 } 86 87 // 製作現代窗戶 88 public override Window CreateWindow() 89 { 90 return new ModernizationWindow(); 91 } 92 93 // 製作現代房門 94 public override Door CreateDoor() 95 { 96 return new ModernizationDoor(); 97 } 98 } 99 100 /// <summary> 101 /// 房頂抽象類,子類的房頂必須繼承該類 102 /// </summary> 103 public abstract class Roof 104 { 105 /// <summary> 106 /// 建立房頂 107 /// </summary> 108 public abstract void Create(); 109 } 110 111 /// <summary> 112 /// 地板抽象類,子類的地板必須繼承該類 113 /// </summary> 114 public abstract class Floor 115 { 116 /// <summary> 117 /// 建立地板 118 /// </summary> 119 public abstract void Create(); 120 } 121 122 /// <summary> 123 /// 窗戶抽象類,子類的窗戶必須繼承該類 124 /// </summary> 125 public abstract class Window 126 { 127 /// <summary> 128 /// 建立窗戶 129 /// </summary> 130 public abstract void Create(); 131 } 132 133 /// <summary> 134 /// 房門抽象類,子類的房門必須繼承該類 135 /// </summary> 136 public abstract class Door 137 { 138 /// <summary> 139 /// 建立房門 140 /// </summary> 141 public abstract void Create(); 142 } 143 144 /// <summary> 145 /// 歐式地板類 146 /// </summary> 147 public class EuropeanFloor : Floor 148 { 149 public override void Create() 150 { 151 Console.WriteLine("建立歐式的地板"); 152 } 153 } 154 155 156 /// <summary> 157 /// 歐式的房頂 158 /// </summary> 159 public class EuropeanRoof : Roof 160 { 161 public override void Create() 162 { 163 Console.WriteLine("建立歐式的房頂"); 164 } 165 } 166 167 168 /// <summary> 169 ///歐式的窗戶 170 /// </summary> 171 public class EuropeanWindow : Window 172 { 173 public override void Create() 174 { 175 Console.WriteLine("建立歐式的窗戶"); 176 } 177 } 178 179 180 /// <summary> 181 /// 歐式的房門 182 /// </summary> 183 public class EuropeanDoor : Door 184 { 185 public override void Create() 186 { 187 Console.WriteLine("建立歐式的房門"); 188 } 189 } 190 191 /// <summary> 192 /// 現代的房頂 193 /// </summary> 194 public class ModernizationRoof : Roof 195 { 196 public override void Create() 197 { 198 Console.WriteLine("建立現代的房頂"); 199 } 200 } 201 202 /// <summary> 203 /// 現代的地板 204 /// </summary> 205 public class ModernizationFloor : Floor 206 { 207 public override void Create() 208 { 209 Console.WriteLine("建立現代的地板"); 210 } 211 } 212 213 /// <summary> 214 /// 現代的窗戶 215 /// </summary> 216 public class ModernizationWindow : Window 217 { 218 public override void Create() 219 { 220 Console.WriteLine("建立現代的窗戶"); 221 } 222 } 223 224 /// <summary> 225 /// 現代的房門 226 /// </summary> 227 public class ModernizationDoor : Door 228 { 229 public override void Create() 230 { 231 Console.WriteLine("建立現代的房門"); 232 } 233 }
3.4、建造者模式(Builder Pattern):該模式要解決的是由多個子部分物件構成的一個複雜物件的建立的問題,該複雜物件構成演算法穩定,各個子部分物件易變化的情況。強調組裝過程的穩定。
動機(Motivate):在軟體系統中,有時候面臨著“一個複雜物件”的建立工作,其通常由各個部分的子物件用一定的演算法構成;由於需求的變化,這個複雜物件的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的演算法卻相對穩定。如何應對這種變化?如何提供一種“封裝機制”來隔離出“複雜物件的各個部分”的變化,從而保持系統中的“穩定構建演算法”不隨著需求改變而改變?
意圖(Intent):將一個複雜物件的構建與其表示相分離,使得同樣的構建過程可以建立不同的表示。
將一個產品的表示形式與產品的組裝過程分割開來,從而可以使同一個組裝過程(這個構建過程是穩定的,也就是演算法)生成具體不同的表現的產品物件。
具體結構圖如下所示:
3.5、原型模式(Prototype Pattern):通過制定例項型別來複制物件
動機(Motivate):在軟體系統中,經常面臨著“某些結構複雜的物件”的建立工作;由於需求的變化,這些物件經常面臨著劇烈的變化,但是它們卻擁有比較穩定一致的介面。如何應對這種變化?如何向“客戶程式(使用這些物件的程式)”隔離出“這些易變物件”,從而使得“依賴這些易變物件的客戶程式”不隨著需求改變而改變?
意圖(Intent):使用原型例項指定建立物件的種類,然後通過拷貝這些原型來建立新的物件。
具體結構圖如下所示:
四、結構型模式
結構型模式主要研究的是類和物件的組合的問題。它包括兩種型別,一是類結構型模式:指的是採用繼承機制來組合實現功能;二是物件結構型模式:指的是通過組合物件的方式來實現新的功能。該系列模式包括:介面卡模式、橋接模式、裝飾者模式、組合模式、外觀模式、享元模式和代理模式。
4.1、介面卡模式(Adapter Pattern):該模式主要關注的是介面轉換的問題,將匹配的介面通過適配對接工作。
動機(Motivate):在軟體系統中,由於應用環境的變化,常常需要將“一些現存的物件”放在新的環境中應用,但是新環境要求的介面是這些現存物件所不滿足的。如何應對這種“遷移的變化”?如何既能利用現有物件的良好實現,同時又能滿足新的應用環境所要求的介面?
意圖(Intent):將一個類的介面轉換成客戶希望的另一個介面。Adapter模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作。
介面卡模式意在轉換介面,它能夠使原本不能再一起工作的兩個類一起工作,所以經常用來在類庫的複用、程式碼遷移等方面。 介面卡模式包括類介面卡模式和物件介面卡模式,
具體結構圖如下所示:
類介面卡模式:
物件介面卡模式:
4.2、橋接模式(Bridge Pattern):該模式注重分離介面與其實現,介面是針對客戶的,介面的內在實現是通過“實現層次”來完成的,支援多維度變化。
動機(Motivate):在很多遊戲場景中,會有這樣的情況:【裝備】本身會有的自己固有的邏輯,比如槍支,會有型號的問題,同時現在很多的遊戲又在不同的介質平臺上執行和使用,這樣就使得遊戲的【裝備】具有了兩個變化的維度——一個變化的維度為“平臺的變化”,另一個變化的維度為“型號的變化”。如果我們要寫程式碼實現這款遊戲,難道我們針對每種平臺都實現一套獨立的【裝備】嗎?複用在哪裡?如何應對這種“多維度的變化”?如何利用物件導向技術來使得【裝備】可以輕鬆地沿著“平臺”和“型號”兩個方向變化,而不引入額外的複雜度?
意圖(Intent):將抽象部分與實現部分分離,使它們都可以獨立地變化。
比如:就拿遊戲裝備來說,“手槍”,抽象部分是指手槍的型號,這個型號可以是G50,G55,針對不同平臺,這些型號有不同的實現,針對這些不同平臺的不同型號的實現,重新抽象,抽象的結構就是“實現的層次”,其實,到此,就形成了兩個抽象層次,第一個層次,是槍支的型號的層次,另一個層次就是針對其實現的一個層次,這樣就做到了抽象和實現的分離。
具體結構圖如下所示:
示例程式碼:
1 namespace 橋接模式的實現 2 { 3 /// <summary> 4 /// 該抽象類就是抽象介面的定義,該型別就相當於是Abstraction型別 5 /// </summary> 6 public abstract class Database 7 { 8 //通過組合方式引用平臺介面,此處就是橋樑,該型別相當於Implementor型別 9 protected PlatformImplementor _implementor; 10 11 //通過構造器注入,初始化平臺實現 12 protected Database(PlatformImplementor implementor) 13 { 14 this._implementor = implementor; 15 } 16 17 //建立資料庫--該操作相當於Abstraction型別的Operation方法 18 public abstract void Create(); 19 } 20 21 /// <summary> 22 /// 該抽象類就是實現介面的定義,該型別就相當於是Implementor型別 23 /// </summary> 24 public abstract class PlatformImplementor 25 { 26 //該方法就相當於Implementor型別的OperationImpl方法 27 public abstract void Process(); 28 } 29 30 /// <summary> 31 /// SqlServer2000版本的資料庫,相當於RefinedAbstraction型別 32 /// </summary> 33 public class SqlServer2000 : Database 34 { 35 //建構函式初始化 36 public SqlServer2000(PlatformImplementor implementor) : base(implementor) { } 37 38 public override void Create() 39 { 40 this._implementor.Process(); 41 } 42 } 43 44 /// <summary> 45 /// SqlServer2005版本的資料庫,相當於RefinedAbstraction型別 46 /// </summary> 47 public class SqlServer2005 : Database 48 { 49 //建構函式初始化 50 public SqlServer2005(PlatformImplementor implementor) : base(implementor) { } 51 52 public override void Create() 53 { 54 this._implementor.Process(); 55 } 56 } 57 58 /// <summary> 59 /// SqlServer2000版本的資料庫針對Unix作業系統具體的實現,相當於ConcreteImplementorA型別 60 /// </summary> 61 public class SqlServer2000UnixImplementor : PlatformImplementor 62 { 63 public override void Process() 64 { 65 Console.WriteLine("SqlServer2000針對Unix的具體實現"); 66 } 67 } 68 69 /// <summary> 70 /// SqlServer2005版本的資料庫針對Unix作業系統的具體實現,相當於ConcreteImplementorB型別 71 /// </summary> 72 public sealed class SqlServer2005UnixImplementor : PlatformImplementor 73 { 74 public override void Process() 75 { 76 Console.WriteLine("SqlServer2005針對Unix的具體實現"); 77 } 78 } 79 80 public class Program 81 { 82 static void Main() 83 { 84 PlatformImplementor SqlServer2000UnixImp = new SqlServer2000UnixImplementor(); 85 //還可以針對不同平臺進行擴充套件,也就是子類化,這個是獨立變化的 86 87 Database SqlServer2000Unix = new SqlServer2000(SqlServer2000UnixImp); 88 //資料庫版本也可以進行擴充套件和升級,也進行獨立的變化。 89 90 //以上就是兩個維度的變化。 91 92 //就可以針對Unix執行操作了 93 SqlServer2000Unix.Create(); 94 } 95 } 96 }
4.3、裝飾模式(Decorator Pattern):該模式注重穩定介面,讓介面不變,在此前提下為物件動態(關鍵點)的擴充套件功能。如果通過繼承,各個功能點的組合就會形成過多的子類,維護起來就是麻煩。
動機(Motivate):在房子裝修的過程中,各種功能可以相互組合,來增加房子的功用。類似的,如果我們在軟體系統中,要給某個型別或者物件增加功能,如果使用“繼承”的方案來寫程式碼,就會出現子類暴漲的情況。比如:IMarbleStyle是大理石風格的一個功能,IKeepWarm是保溫的一個介面定義,IHouseSecurity是房子安全的一個介面,就三個介面來說,House是我們房子,我們的房子要什麼功能就實現什麼介面,如果房子要的是複合功能,介面不同的組合就有不同的結果,這樣就導致我們子類膨脹嚴重,如果需要在增加功能,子類會成指數增長。這個問題的根源在於我們“過度地使用了繼承來擴充套件物件的功能”,由於繼承為型別引入的靜態特質(所謂靜態特質,就是說如果想要某種功能,我們必須在編譯的時候就要定義這個類,這也是強型別語言的特點。靜態,就是指在編譯的時候要確定的東西;動態,是指執行時確定的東西),使得這種擴充套件方式缺乏靈活性;並且隨著子類的增多(擴充套件功能的增多),各種子類的組合(擴充套件功能的組合)會導致更多子類的膨脹(多繼承)。如何使“物件功能的擴充套件”能夠根據需要來動態(即執行時)地實現?同時避免“擴充套件功能的增多”帶來的子類膨脹問題?從而使得任何“功能擴充套件變化”所導致的影響降為最低?
意圖(Intent):動態地給一個物件增加一些額外的職責。就增加功能而言,Decorator模式比生成子類更為靈活。
具體結構圖如下所示:
示例程式碼:
1 namespace 裝飾模式的實現 2 { 3 /// <summary> 4 /// 該抽象類就是房子抽象介面的定義,該型別就相當於是Component型別,是餃子餡,需要裝飾的,需要包裝的 5 /// </summary> 6 public abstract class House 7 { 8 //房子的裝修方法--該操作相當於Component型別的Operation方法 9 public abstract void Renovation(); 10 } 11 12 /// <summary> 13 /// 該抽象類就是裝飾介面的定義,該型別就相當於是Decorator型別,如果需要具體的功能,可以子類化該型別 14 /// </summary> 15 public abstract class DecorationStrategy:House //關鍵點之二,體現關係為Is-a,有這這個關係,裝飾的類也可以繼續裝飾了 16 { 17 //通過組合方式引用Decorator型別,該型別實施具體功能的增加 18 //這是關鍵點之一,包含關係,體現為Has-a 19 protected House _house; 20 21 //通過構造器注入,初始化平臺實現 22 protected DecorationStrategy(House house) 23 { 24 this._house=house; 25 } 26 27 //該方法就相當於Decorator型別的Operation方法 28 public override void Renovation() 29 { 30 if(this._house!=null) 31 { 32 this._house.Renovation(); 33 } 34 } 35 } 36 37 /// <summary> 38 /// PatrickLiu的房子,我要按我的要求做房子,相當於ConcreteComponent型別,這就是我們具體的餃子餡,我個人比較喜歡韭菜餡 39 /// </summary> 40 public sealed class PatrickLiuHouse:House 41 { 42 public override void Renovation() 43 { 44 Console.WriteLine("裝修PatrickLiu的房子"); 45 } 46 } 47 48 49 /// <summary> 50 /// 具有安全功能的裝置,可以提供監視和報警功能,相當於ConcreteDecoratorA型別 51 /// </summary> 52 public sealed class HouseSecurityDecorator:DecorationStrategy 53 { 54 public HouseSecurityDecorator(House house):base(house){} 55 56 public override void Renovation() 57 { 58 base.Renovation(); 59 Console.WriteLine("增加安全系統"); 60 } 61 } 62 63 /// <summary> 64 /// 具有保溫介面的材料,提供保溫功能,相當於ConcreteDecoratorB型別 65 /// </summary> 66 public sealed class KeepWarmDecorator:DecorationStrategy 67 { 68 public KeepWarmDecorator(House house):base(house){} 69 70 public override void Renovation() 71 { 72 base.Renovation(); 73 Console.WriteLine("增加保溫的功能"); 74 } 75 } 76 77 public class Program 78 { 79 static void Main() 80 { 81 //這就是我們的餃子餡,需要裝飾的房子 82 House myselfHouse=new PatrickLiuHouse(); 83 84 DecorationStrategy securityHouse=new HouseSecurityDecorator(myselfHouse); 85 securityHouse.Renovation(); 86 //房子就有了安全系統了 87 88 //如果我既要安全系統又要保暖呢,繼續裝飾就行 89 DecorationStrategy securityAndWarmHouse=new HouseSecurityDecorator(securityHouse); 90 securityAndWarmHouse.Renovation(); 91 } 92 } 93 }
1 namespace 命令模式的實現 2 { 3 /// <summary> 4 /// 俗話說:“好吃不如餃子,舒服不如倒著”。今天奶奶發話要吃他大孫子和孫媳婦包的餃子。今天還拿吃餃子這件事來說說命令模式的實現吧。 5 /// </summary> 6 class Client 7 { 8 static void Main(string[] args) 9 { 10 //奶奶想吃豬肉大蔥餡的餃子 11 PatrickLiuAndWife liuAndLai = new PatrickLiuAndWife();//命令接受者 12 Command command = new MakeDumplingsCommand(liuAndLai);//命令 13 PaPaInvoker papa = new PaPaInvoker(command); //命令請求者 14 15 //奶奶釋出命令 16 papa.ExecuteCommand(); 17 18 19 Console.Read(); 20 } 21 } 22 23 //這個型別就是請求者角色--也就是我爸爸的角色,告訴奶奶要吃餃子 24 public sealed class PaPaInvoker 25 { 26 //我爸爸從奶奶那裡接受到的命令 27 private Command _command; 28 29 //爸爸開始接受具體的命令 30 public PaPaInvoker(Command command) 31 { 32 this._command = command; 33 } 34 35 //爸爸給我們下達命令 36 public void ExecuteCommand() 37 { 38 _command.MakeDumplings(); 39 } 40 } 41 42 //該型別就是抽象命令角色--Commmand,定義了命令的抽象介面,任務是包餃子 43 public abstract class Command 44 { 45 //真正任務的接受者 46 protected PatrickLiuAndWife _worker; 47 48 protected Command(PatrickLiuAndWife worker) 49 { 50 _worker = worker; 51 } 52 53 //該方法就是抽象命令物件Command的Execute方法 54 public abstract void MakeDumplings(); 55 } 56 57 //該型別是具體命令角色--ConcreteCommand,這個命令完成製作“豬肉大蔥餡”的餃子 58 public sealed class MakeDumplingsCommand : Command 59 { 60 public MakeDumplingsCommand(PatrickLiuAndWife worker) : base(worker) { } 61 62 //執行命令--包餃子 63 public override void MakeDumplings() 64 { 65 //執行命令---包餃子 66 _worker.Execute("今天包的是農家豬肉和農家大蔥餡的餃子"); 67 } 68 } 69 70 //該型別是具體命令接受角色Receiver,具體包餃子的行為是我們夫妻倆來完成的 71 public sealed class PatrickLiuAndWife 72 { 73 //這個方法相當於Receiver型別的Action方法 74 public void Execute(string job) 75 { 76 Console.WriteLine(job); 77 } 78 } 79 }
4.4、組合模式(Composite Pattern):該模式著重解決統一介面的問題,將“一對多”的關係轉化為“一對一”的關係,讓使用葉子節點和樹幹節點保持介面一致。
動機(Motivate):客戶程式碼過多地依賴於物件容器(物件容器是物件的容器,細細評味)複雜的內部實現結構,物件容器內部實現結構(而非抽象介面)的變化將引起客戶程式碼的頻繁變化,帶來了程式碼的維護性、擴充套件性等方面的弊端。如何將“客戶程式碼與複雜的物件容器內部結構”解耦?如何讓物件容器自己來實現自身的複雜結構,從而使得客戶程式碼就像處理簡單物件一樣來處理複雜的物件容器?
意圖(Intent):將物件組合成樹形結構以表示“部分-整體”的層次結構。Composite使得使用者對單個物件和組合物件的使用具有一致性。
具體結構圖如下所示:
4.5、外觀模式(Facade Pattern):該模式注重簡化介面,簡化元件系統與外部客戶程式的依賴關係,是Lod(迪米特原則,也叫:最少知識原則)原則的最好的體現,
動機(Motivate):在軟體系統開發的過程中,當元件的客戶(即外部介面,或客戶程式)和元件中各種複雜的子系統有了過多的耦合,隨著外部客戶程式和各子系統的演化,這種過多的耦合面臨很多變化的挑戰。如何簡化外部客戶程式和系統間的互動介面?如何將外部客戶程式的演化和內部子系統的變化之間的依賴相互解耦?
意圖(Intent):為子系統中的一組介面提供一個一致的介面,Facade模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。
具體結構圖如下所示:
4.6、享元模式(Flyweight Pattern):該模式注重保留介面,在內部使用共享技術對物件儲存進行優化
動機(Motivate):在軟體系統中,採用純粹物件方案的問題在於大量細粒度的物件會很快充斥在系統中,從而帶來很高的執行時代價——主要指記憶體需求方面的代價。如何在避免大量細粒度物件問題的同時,讓外部客戶程式仍然能夠透明地使用物件導向的方式來進行操作?
意圖(Intent):運用共享技術有效地支援大量細粒度的物件。
在.NET類庫中,String類的實現就使用了享元模式,String類採用字串駐留池的來使字串進行共享。
具體結構圖如下所示:
4.7、代理模式(Proxy Pattern):該模式注重假借代理介面,控制對真實物件的訪問,通過增加間接層來實現靈活控制,比如可以在訪問正真物件之前進行許可權驗證,生活中的明星的代理就是很好的例子,要想接觸明星,先要和他的經紀人打交道。
動機(Motivate):在物件導向系統中,有些物件由於某種原因(比如物件建立的開銷很大,或者某些操作需要安全控制,或者需要程式外的訪問等),直接訪問會給使用者、或者系統結構帶來很多麻煩。如何在不失去透明操作物件的同時來管理/控制這些物件特有的複雜性?增加一層間接層是軟體開發中常見的解決方式。
意圖(Intent):為其他物件提供一種代理以控制對這個物件的訪問。
具體結構圖如下所示:
五、行為型模式
行為型模式主要討論的是在不同物件之間劃分責任和演算法的抽象化的問題。行為型模式又分為類的行為模式和物件的行為模式兩種。
類的行為模式——使用繼承關係在幾個類之間分配行為。
物件的行為模式——使用物件聚合的方式來分配行為。
行為型模式包括11種模式:模板方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、狀態模式、策略模式、責任鏈模式、訪問者模式、直譯器模式和備忘錄模式。
5.1、模板方法模式(Template Method Pattern):該模式注重對演算法結構的封裝,定義演算法骨架,並且穩定,但是支援演算法子步驟的變化。
動機(Motivate):在軟體構建過程中,對於某一項任務,它常常有穩定的整體操作結構,但各個子步驟卻有很多改變的需求,或者由於固有的原因(比如框架與應用之間的關係)而無法和任務的整體結構同時實現。如何在確定穩定操作結構的前提下,來靈活應對各個子步驟的變化或者晚期實現需求?
意圖(Intent):定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。
具體結構圖如下所示:
5.2、命令模式(Command Pattern):該模式注重將請求封裝為物件,通過將一組行為抽象為物件,實現行為請求者和行為實現者之間的解耦。也可以實現“撤銷/重做”的功能,類似“巨集”的功能實現也很容易。
動機(Motivate):在軟體構建過程中,“行為請求者”與“行為實現者”通常呈現一種“緊耦合”。但在某些場合——比如需要對行為進行“記錄、撤銷/重做(undo/redo)、事務”等處理,這種無法抵禦變化的緊耦合是不合適的。在這種情況下,如何將“行為請求者”與“行為實現者”解耦?將一組行為抽象為物件,可以實現二者之間的鬆耦合。
意圖(Intent):將一個請求封裝為一個物件,從而使你可用不同的請求對客戶(客戶程式,也是行為的請求者)進行引數化;對請求排隊或記錄請求日誌,以及支援可撤銷的操作。命令模式的實現可以提供命令的撤銷和恢復功能。
具體結構圖如下所示:
程式碼例項:
1 namespace 命令模式的實現 2 { 3 /// <summary> 4 /// 俗話說:“好吃不如餃子,舒服不如倒著”。今天奶奶發話要吃他大孫子和孫媳婦包的餃子。今天還拿吃餃子這件事來說說命令模式的實現吧。 5 /// </summary> 6 class Client 7 { 8 static void Main(string[] args) 9 { 10 //奶奶想吃豬肉大蔥餡的餃子 11 PatrickLiuAndWife liuAndLai = new PatrickLiuAndWife();//命令接受者 12 Command command = new MakeDumplingsCommand(liuAndLai);//命令 13 PaPaInvoker papa = new PaPaInvoker(command); //命令請求者 14 15 //奶奶釋出命令 16 papa.ExecuteCommand(); 17 18 19 Console.Read(); 20 } 21 } 22 23 //這個型別就是請求者角色--也就是我爸爸的角色,告訴奶奶要吃餃子 24 public sealed class PaPaInvoker 25 { 26 //我爸爸從奶奶那裡接受到的命令 27 private Command _command; 28 29 //爸爸開始接受具體的命令 30 public PaPaInvoker(Command command) 31 { 32 this._command = command; 33 } 34 35 //爸爸給我們下達命令 36 public void ExecuteCommand() 37 { 38 _command.MakeDumplings(); 39 } 40 } 41 42 //該型別就是抽象命令角色--Commmand,定義了命令的抽象介面,任務是包餃子 43 public abstract class Command 44 { 45 //真正任務的接受者 46 protected PatrickLiuAndWife _worker; 47 48 protected Command(PatrickLiuAndWife worker) 49 { 50 _worker = worker; 51 } 52 53 //該方法就是抽象命令物件Command的Execute方法 54 public abstract void MakeDumplings(); 55 } 56 57 //該型別是具體命令角色--ConcreteCommand,這個命令完成製作“豬肉大蔥餡”的餃子 58 public sealed class MakeDumplingsCommand : Command 59 { 60 public MakeDumplingsCommand(PatrickLiuAndWife worker) : base(worker) { } 61 62 //執行命令--包餃子 63 public override void MakeDumplings() 64 { 65 //執行命令---包餃子 66 _worker.Execute("今天包的是農家豬肉和農家大蔥餡的餃子"); 67 } 68 } 69 70 //該型別是具體命令接受角色Receiver,具體包餃子的行為是我們夫妻倆來完成的 71 public sealed class PatrickLiuAndWife 72 { 73 //這個方法相當於Receiver型別的Action方法 74 public void Execute(string job) 75 { 76 Console.WriteLine(job); 77 } 78 } 79 }
5.3、迭代器模式(Iterator Pattern):該模式注重封裝對集合的操作,支援集合例項的變化,遮蔽集合物件內部複雜結構,提供客戶程式對它的透明遍歷。
動機(Motivate):在軟體構建過程中,集合物件內部結構常常變化各異。但對於這些集合物件,我們希望在不暴露其內部結構的同時,可以讓外部客戶程式碼透明地訪問其中包含的元素;同時這種“透明遍歷”也為“同一種演算法在多種集合物件上進行操作”提供了可能。使用物件導向技術將這種遍歷機制抽象為“迭代器物件”為“應對變化中的集合物件”提供了一種優雅的方式。
意圖(Intent): 提供一種方法順序訪問一個聚合物件中的各個元素,而又不暴露該物件的內部表示。
具體結構圖如下所示:
5.4、觀察者模式(Observer Pattern):該模式注重的是變化通知,變化通知指目標物件發生變化,依賴的物件就能獲得通知並進行相應操作,這是一種“一對多”的關係。
動機(Motivate):在軟體構建過程中,我們需要為某些物件建立一種“通知依賴關係”——一個物件(目標物件)的狀態發生改變,所有的依賴物件(觀察者物件)都將得到通知。如果這樣的依賴關係過於緊密,將使軟體不能很好地抵禦變化。使用物件導向技術,可以將這種依賴關係弱化,並形成一種穩定的依賴關係。從而實現軟體體系結構的鬆耦合。
意圖(Intent):定義物件間的一種一對多的依賴關係,以便當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並自動更新。
具體結構圖如下所示:
5.5、中介者模式(Mediator Pattern):該模式注重封裝物件間的互動,通過封裝一系列物件之間的複雜互動,使他們不需要顯式相互引用,實現解耦。
動機(Motivate):在軟體構建過程中,經常會出現多個物件互相關聯互動的情況,物件之間常常會維持一種複雜的引用關係,如果遇到一些需求的更改,這種直接的引用關係將面臨不斷地變化。在這種情況下,我們可使用一個“中介物件”來管理物件間的關聯關係,避免相互互動的物件之間的緊耦合引用關係,從而更好地抵禦變化。
意圖(Intent):定義了一箇中介物件來封裝一系列物件之間的互動關係。中介者使各個物件之間不需要顯式地相互引用,從而使耦合性降低,而且可以獨立地改變它們之間的互動行為。
具體的結構圖如下所示:
程式碼例項:
1 namespace 中介者模式的實現 2 { 3 //抽象中介者角色 4 public interface Mediator 5 { 6 void Command(Department department); 7 } 8 9 //總經理--相當於具體中介者角色 10 public sealed class President : Mediator 11 { 12 //總經理有各個部門的管理許可權 13 private Financial _financial; 14 private Market _market; 15 private Development _development; 16 17 public void SetFinancial(Financial financial) 18 { 19 this._financial = financial; 20 } 21 public void SetDevelopment(Development development) 22 { 23 this._development = development; 24 } 25 public void SetMarket(Market market) 26 { 27 this._market = market; 28 } 29 30 public void Command(Department department) 31 { 32 if (department.GetType() == typeof(Market)) 33 { 34 _financial.Process(); 35 } 36 } 37 } 38 39 //同事類的介面 40 public abstract class Department 41 { 42 //持有中介者(總經理)的引用 43 private Mediator mediator; 44 45 protected Department(Mediator mediator) 46 { 47 this.mediator = mediator; 48 } 49 50 public Mediator GetMediator 51 { 52 get { return mediator; } 53 private set { this.mediator = value; } 54 } 55 56 //做本部門的事情 57 public abstract void Process(); 58 59 //向總經理髮出申請 60 public abstract void Apply(); 61 } 62 63 //開發部門 64 public sealed class Development : Department 65 { 66 public Development(Mediator m) : base(m) { } 67 68 public override void Process() 69 { 70 Console.WriteLine("我們是開發部門,要進行專案開發,沒錢了,需要資金支援!"); 71 } 72 73 public override void Apply() 74 { 75 Console.WriteLine("專心科研,開發專案!"); 76 } 77 } 78 79 //財務部門 80 public sealed class Financial : Department 81 { 82 public Financial(Mediator m) : base(m) { } 83 84 public override void Process() 85 { 86 Console.WriteLine("彙報工作!沒錢了,錢太多了!怎麼花?"); 87 } 88 89 public override void Apply() 90 { 91 Console.WriteLine("數錢!"); 92 } 93 } 94 95 //市場部門 96 public sealed class Market : Department 97 { 98 public Market(Mediator mediator) : base(mediator) { } 99 100 public override void Process() 101 { 102 Console.WriteLine("彙報工作!專案承接的進度,需要資金支援!"); 103 GetMediator.Command(this); 104 } 105 106 public override void Apply() 107 { 108 Console.WriteLine("跑去接專案!"); 109 } 110 } 111 112 113 class Program 114 { 115 static void Main(String[] args) 116 { 117 President mediator = new President(); 118 Market market = new Market(mediator); 119 Development development = new Development(mediator); 120 Financial financial = new Financial(mediator); 121 122 mediator.SetFinancial(financial); 123 mediator.SetDevelopment(development); 124 mediator.SetMarket(market); 125 126 market.Process(); 127 market.Apply(); 128 129 Console.Read(); 130 } 131 } 132 }
5.6、狀態模式(State Pattern):該模式注重封裝與狀態相關的行為(定義狀態類,會把和一個狀態相關的操作都放到這個類裡面),支援狀態的變化,從而在其內部狀態改變時改變它的行為。
動機(Motivate):在軟體構建過程中,某些物件的狀態如果改變,其行為也會隨之而發生變化,比如文件處於只讀狀態,其支援的行為和讀寫狀態支援的行為就可能完全不同。如何在執行時根據物件的狀態來透明地更改物件的行為?而不會為物件操作和狀態轉化之間引入緊耦合?
意圖(Intent):允許一個物件在其內部狀態改變時改變它的行為。從而使物件看起來似乎修改了其行為。
具體結構圖如下所示:
程式碼例項:
1 namespace 狀態模式的實現 2 { 3 //環境角色---相當於Context型別 4 public sealed class Order 5 { 6 private State current; 7 8 public Order() 9 { 10 //工作狀態初始化為上午工作狀態 11 current = new WaitForAcceptance(); 12 IsCancel = false; 13 } 14 private double minute; 15 public double Minute 16 { 17 get { return minute; } 18 set { minute = value; } 19 } 20 21 public bool IsCancel { get; set; } 22 23 private bool finish; 24 public bool TaskFinished 25 { 26 get { return finish; } 27 set { finish = value; } 28 } 29 public void SetState(State s) 30 { 31 current = s; 32 } 33 public void Action() 34 { 35 current.Process(this); 36 } 37 } 38 39 //抽象狀態角色---相當於State型別 40 public interface State 41 { 42 //處理訂單 43 void Process(Order order); 44 } 45 46 //等待受理--相當於具體狀態角色 47 public sealed class WaitForAcceptance : State 48 { 49 public void Process(Order order) 50 { 51 System.Console.WriteLine("我們開始受理,準備備貨!"); 52 if (order.Minute < 30 && order.IsCancel) 53 { 54 System.Console.WriteLine("接受半個小時之內,可以取消訂單!"); 55 order.SetState(new CancelOrder()); 56 order.TaskFinished = true; 57 order.Action(); 58 } 59 order.SetState(new AcceptAndDeliver()); 60 order.TaskFinished = false; 61 order.Action(); 62 } 63 } 64 65 //受理髮貨---相當於具體狀態角色 66 public sealed class AcceptAndDeliver : State 67 { 68 public void Process(Order order) 69 { 70 System.Console.WriteLine("我們貨物已經準備好,可以發貨了,不可以撤銷訂單!"); 71 if (order.Minute < 30 && order.IsCancel) 72 { 73 System.Console.WriteLine("接受半個小時之內,可以取消訂單!"); 74 order.SetState(new CancelOrder()); 75 order.TaskFinished = true; 76 order.Action(); 77 } 78 if (order.TaskFinished==false) 79 { 80 order.SetState(new Success()); 81 order.Action(); 82 } 83 } 84 } 85 86 //交易成功---相當於具體狀態角色 87 public sealed class Success : State 88 { 89 public void Process(Order order) 90 { 91 System.Console.WriteLine("訂單結算"); 92 order.SetState(new ConfirmationReceipt()); 93 order.TaskFinished = true; 94 order.Action(); 95 } 96 } 97 98 //確認收貨---相當於具體狀態角色 99 public sealed class ConfirmationReceipt : State 100 { 101 public void Process(Order order) 102 { 103 System.Console.WriteLine("檢查貨物,沒問題可以就可以簽收!"); 104 order.SetState(new ConfirmationReceipt()); 105 order.TaskFinished = true; 106 order.Action(); 107 } 108 } 109 110 //取消訂單---相當於具體狀態角色 111 public sealed class CancelOrder : State 112 { 113 public void Process(Order order) 114 { 115 System.Console.WriteLine("檢查貨物,有問題,取消訂單!"); 116 order.SetState(new CancelOrder()); 117 order.TaskFinished = true; 118 order.Action(); 119 } 120 } 121 122 123 public class Client 124 { 125 public static void Main(String[] args) 126 { 127 //訂單 128 Order order = new Order(); 129 order.Minute = 9; 130 order.Action(); 131 //可以取消訂單 132 order.IsCancel = true; 133 order.Minute = 20; 134 order.Action(); 135 order.Minute = 33; 136 order.Action(); 137 order.Minute = 43; 138 order.Action(); 139 140 Console.Read(); 141 } 142 } 143 }
5.7、策略模式(Stragety Pattern):該模式注重封裝演算法,這裡面沒有演算法骨架,一種演算法就是一種解決方案,一種方法策略。支援演算法的變化,通過封裝一系列演算法,可以做到演算法的替換來滿足客戶的需求。
動機(Motivate): 在軟體構建過程中,某些物件使用的演算法可能多種多樣,經常改變,如果將這些演算法都編碼到物件中,將會使物件變得異常複雜;而且有時候支援不使用的演算法也是一個效能負擔。如何在執行時根據需要透明地更改物件的演算法?將演算法與物件本身解耦,從而避免上述問題?
意圖(Intent):定義一系列演算法,把它們一個個封裝起來,並且使它們可互相替換。該模式使得演算法可獨立於使用它的客戶而變化。
具體結構圖如下所示:
5.8、責任鏈模式(Chain of Responsibility Pattern):該模式注重封裝物件責任,支援責任的變化,通過動態構建職責鏈,實現業務處理。在現實生活中,請假流程,採購流程等都是職責鏈模式很好的例子。
動機(Motivate):在軟體構建過程中,一個請求可能被多個物件處理,但是每個請求在執行時只能有一個接受者,如果顯示指定,將必不可少地帶來請求傳送者與接受者的緊耦合。如何使請求的傳送者不需要指定具體的接受者,讓請求的接受者自己在執行時決定來處理請求,從而使兩者解耦。
意圖(Intent):避免請求傳送者與接收者耦合在一起,讓多個物件都有可能接受請求,將這些物件連線成一條鏈,並且沿著這條鏈傳遞請求,知道有物件處理它為止。
具體結構圖如下所示:
5.9、訪問者模式(Visitor Pattern):該模式注重封裝物件操作變化,支援在執行時為類結構新增新的操作,在類層次結構中,在不改變各類的前提下定義作用於這些類例項的新的操作。
動機(Motivate):在軟體構建過程中,由於需求的改變,某些類層次結構中常常需要增加新的行為(方法),如果直接在基類中做這樣的更改,將會給子類帶來很繁重的變更負擔,甚至破壞原有設計。如何在不更改類層次結構的前提下,在執行時根據需要透明地為類層次結構上的各個類動態新增新的操作,從而避免上述問題?
意圖(Intent):表示一個作用於某物件結構中的各個元素的操作。它可以在不改變各元素的類的前提下定義作用於這些元素的新的操作。
具體結構圖如下所示:
程式碼例項:
1 namespace Vistor 2 { 3 //抽象圖形定義---相當於“抽象節點角色”Element 4 public abstract class Shape 5 { 6 //畫圖形 7 public abstract void Draw(); 8 //外界注入具體訪問者 9 public abstract void Accept(ShapeVisitor visitor); 10 } 11 12 //抽象訪問者 Visitor 13 public abstract class ShapeVisitor 14 { 15 public abstract void Visit(Rectangle shape); 16 17 public abstract void Visit(Circle shape); 18 19 public abstract void Visit(Line shape); 20 21 //這裡有一點要說:Visit方法的引數可以寫成Shape嗎?就是這樣 Visit(Shape shape),當然可以,但是ShapeVisitor子類Visit方法就需要判斷當前的Shape是什麼型別,是Rectangle型別,是Circle型別,或者是Line型別。 22 } 23 24 //具體訪問者 ConcreteVisitor 25 public sealed class CustomVisitor : ShapeVisitor 26 { 27 //針對Rectangle物件 28 public override void Visit(Rectangle shape) 29 { 30 Console.WriteLine("針對Rectangle新的操作!"); 31 } 32 //針對Circle物件 33 public override void Visit(Circle shape) 34 { 35 Console.WriteLine("針對Circle新的操作!"); 36 } 37 //針對Line物件 38 public override void Visit(Line shape) 39 { 40 Console.WriteLine("針對Line新的操作!"); 41 } 42 } 43 44 //矩形----相當於“具體節點角色” ConcreteElement 45 public sealed class Rectangle : Shape 46 { 47 public override void Draw() 48 { 49 Console.WriteLine("矩形我已經畫好!"); 50 } 51 52 public override void Accept(ShapeVisitor visitor) 53 { 54 visitor.Visit(this); 55 } 56 } 57 58 //圓形---相當於“具體節點角色”ConcreteElement 59 public sealed class Circle : Shape 60 { 61 public override void Draw() 62 { 63 Console.WriteLine("圓形我已經畫好!"); 64 } 65 66 public override void Accept(ShapeVisitor visitor) 67 { 68 visitor.Visit(this); 69 } 70 } 71 72 //直線---相當於“具體節點角色” ConcreteElement 73 public sealed class Line : Shape 74 { 75 public override void Draw() 76 { 77 Console.WriteLine("直線我已經畫好!"); 78 } 79 80 public override void Accept(ShapeVisitor visitor) 81 { 82 visitor.Visit(this); 83 } 84 } 85 86 //結構物件角色 87 internal class AppStructure 88 { 89 private ShapeVisitor _visitor; 90 91 public AppStructure(ShapeVisitor visitor) 92 { 93 this._visitor = visitor; 94 } 95 96 public void Process(Shape shape) 97 { 98 shape.Accept(_visitor); 99 } 100 } 101 102 class Program 103 { 104 static void Main(string[] args) 105 { 106 //如果想執行新增加的操作 107 ShapeVisitor visitor = new CustomVisitor(); 108 AppStructure app = new AppStructure(visitor); 109 110 Shape shape = new Rectangle(); 111 shape.Draw();//執行自己的操作 112 app.Process(shape);//執行新的操作 113 114 115 shape = new Circle(); 116 shape.Draw();//執行自己的操作 117 app.Process(shape);//執行新的操作 118 119 120 shape = new Line(); 121 shape.Draw();//執行自己的操作 122 app.Process(shape);//執行新的操作 123 124 125 Console.ReadLine(); 126 } 127 } 128 }
5.10、備忘錄模式(Memento Pattern):該模式注重封裝物件狀態變化,支援狀態儲存、恢復。現實生活中的手機通訊錄備忘錄,作業系統備份,資料庫備份等都是備忘錄模式的應用。
動機(Motivate):在軟體構建過程中,某些物件的狀態在轉換的過程中,可能由於某種需要,要求程式能夠回溯到物件之前處於某個點時的狀態。如果使用一些公有介面來讓其他物件得到物件的狀態,便會暴露物件的細節實現。如何實現物件狀態的良好儲存與恢復,但同時又不會因此而破壞物件本身的封裝性?
意圖(Intent):在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態(如果沒有這個關鍵點,其實深拷貝就可以解決問題)。這樣以後就可以將該物件恢復到原先儲存的狀態。
具體的結構圖如下所示:
程式碼例項:
1 namespace MementoPattern 2 { 3 // 聯絡人--需要備份的資料,是狀態資料,沒有操作 4 public sealed class ContactPerson 5 { 6 //姓名 7 public string Name { get; set; } 8 9 //電話號碼 10 public string MobileNumber { get; set; } 11 } 12 13 // 發起人--相當於【發起人角色】Originator 14 public sealed class MobileBackOriginator 15 { 16 // 發起人需要儲存的內部狀態 17 private List<ContactPerson> _personList; 18 19 20 public List<ContactPerson> ContactPersonList 21 { 22 get 23 { 24 return this._personList; 25 } 26 27 set 28 { 29 this._personList = value; 30 } 31 } 32 //初始化需要備份的電話名單 33 public MobileBackOriginator(List<ContactPerson> personList) 34 { 35 if (personList != null) 36 { 37 this._personList = personList; 38 } 39 else 40 { 41 throw new ArgumentNullException("引數不能為空!"); 42 } 43 } 44 45 // 建立備忘錄物件例項,將當期要儲存的聯絡人列表儲存到備忘錄物件中 46 public ContactPersonMemento CreateMemento() 47 { 48 return new ContactPersonMemento(new List<ContactPerson>(this._personList)); 49 } 50 51 // 將備忘錄中的資料備份還原到聯絡人列表中 52 public void RestoreMemento(ContactPersonMemento memento) 53 { 54 this.ContactPersonList = memento.ContactPersonListBack; 55 } 56 57 public void Show() 58 { 59 Console.WriteLine("聯絡人列表中共有{0}個人,他們是:", ContactPersonList.Count); 60 foreach (ContactPerson p in ContactPersonList) 61 { 62 Console.WriteLine("姓名: {0} 號碼: {1}", p.Name, p.MobileNumber); 63 } 64 } 65 } 66 67 // 備忘錄物件,用於儲存狀態資料,儲存的是當時物件具體狀態資料--相當於【備忘錄角色】Memeto 68 public sealed class ContactPersonMemento 69 { 70 // 儲存發起人建立的電話名單資料,就是所謂的狀態 71 public List<ContactPerson> ContactPersonListBack { get; private set; } 72 73 public ContactPersonMemento(List<ContactPerson> personList) 74 { 75 ContactPersonListBack = personList; 76 } 77 } 78 79 // 管理角色,它可以管理【備忘錄】物件,如果是儲存多個【備忘錄】物件,當然可以對儲存的物件進行增、刪等管理處理---相當於【管理者角色】Caretaker 80 public sealed class MementoManager 81 { 82 //如果想儲存多個【備忘錄】物件,可以通過字典或者堆疊來儲存,堆疊物件可以反映儲存物件的先後順序 83 //比如:public Dictionary<string, ContactPersonMemento> ContactPersonMementoDictionary { get; set; } 84 public ContactPersonMemento ContactPersonMemento { get; set; } 85 } 86 87 class Program 88 { 89 static void Main(string[] args) 90 { 91 List<ContactPerson> persons = new List<ContactPerson>() 92 { 93 new ContactPerson() { Name="黃飛鴻", MobileNumber = "13533332222"}, 94 new ContactPerson() { Name="方世玉", MobileNumber = "13966554433"}, 95 new ContactPerson() { Name="洪熙官", MobileNumber = "13198765544"} 96 }; 97 98 //手機名單發起人 99 MobileBackOriginator mobileOriginator = new MobileBackOriginator(persons); 100 mobileOriginator.Show(); 101 102 // 建立備忘錄並儲存備忘錄物件 103 MementoManager manager = new MementoManager(); 104 manager.ContactPersonMemento = mobileOriginator.CreateMemento(); 105 106 // 更改發起人聯絡人列表 107 Console.WriteLine("----移除最後一個聯絡人--------"); 108 mobileOriginator.ContactPersonList.RemoveAt(2); 109 mobileOriginator.Show(); 110 111 // 恢復到原始狀態 112 Console.WriteLine("-------恢復聯絡人列表------"); 113 mobileOriginator.RestoreMemento(manager.ContactPersonMemento); 114 mobileOriginator.Show(); 115 116 Console.Read(); 117 } 118 } 119 }
5.11、直譯器模式(Interpreter Pattern):該模式注重封裝特定領域變化,將特定領域的問題表達為某種語法規則下的句子,然後構建一個直譯器來解釋這樣的句子,從而達到解決問題的目的。C#的編譯器,中英翻譯工具,正規表示式都是直譯器應用的很好例子。
動機(Motivate):在軟體構建過程中,如果某一特定領域的問題比較複雜,類似的模式不斷重複出現,如果使用普通的程式設計方式來實現將面臨非常頻繁的變化。在這種情況下,將特定領域的問題表達為某種語法規則下的句子,然後構建一個直譯器來解釋這樣的句子,從而達到解決問題的目的。
意圖(Intent):給定一個語言,定義它的文法的一種表示,並定義一種直譯器,這個直譯器使用該表示來解釋語言中的句子。
具體的結構圖如下所示:
六、總結
C#版本23種物件導向的設計模式,終於寫完了,今天是一個總結性的文章。各個模式都列了出來,每個模式的【動機】、【意圖】和【結構圖】也寫了出來,可以方便大家檢視。重新自己寫了一遍,感覺很多都不一樣了。理解更深刻了,學無止境,繼續前進吧。