對於設計模式, 從本質上說, 其最大的用途就是適應需求的變化. 由於有了設計模式,咱們能夠在設計階段就爲將來可能發生的變化留下足夠的空間.編程
咱們經過一個建造現代化養豬場的故事, 來討論一下設計模式與須要變化之間的關係.設計模式
(一)設計模式最根本的意圖是適應需求的變化dom
一個機器人研發小組研製了一種能自動餵豬的機器人, 因而有人投資興建了一座由機器人管理的養豬場. 這個養豬場要飼養的豬的品種包括:函數
下面咱們來討論機器人小組的設計方案: this
圖v1-1spa
用UML繪製的養豬場v1.0軟件動做機制如圖V1-1所示。最初的設計由於豬場只引進了大白豬品種,因此沒有考慮飼養其它種類豬的狀況。設計
一個餵豬機器人管理了若干頭大白豬。因爲豬天性懶惰,機器人必須把飼料放到豬嘴邊,而後吆喝一句,「大白豬,吃!」,豬纔會進食。3d
在v1.0版的程序動做下,養豬場運行情況至關不錯,每頭豬都養得膘肥休壯,若是不是企業要不斷的追求剩餘價值,咱們的故事也許就到此結束了。code
隨着養豬場的蓬勃發展,養豬場老闆決定進軍國際市場。但是,不一樣地方的人喜歡吃不一樣品種的豬。爲了適應這一需求的變化,養豬場新引進了一批長白豬。orm
問題出現了:餵豬機器人照例把飼料準備好,拿到每一頭豬面前,大叫一聲:」大白豬,吃!「,大白豬能像往常同樣愉快地進食。但輪到長白豬時,長白豬無動於衷。這下急壞了養豬場的老闆。爲了解決問題,餵豬機器人的研發團隊緊急出動,當晚便改好了程序。
V2.0 程序如圖v2-1所示:
圖v2-1
通過這次修改,餵豬機器人在對待每一頭豬時,會先辯認出這頭豬的類型,若是這是一頭大白豬,它就會大叫一聲」大白豬,吃!「,若是是一頭長白豬,就叫」長白豬,吃「,通過這一個性,養豬場又恢復了平靜。
可剛過了幾天,相似的問題又再次出現,養豬場引進了幾頭波中豬!這下,機器人又不知道怎麼餵了。研發團隊又準備大動干戈、修改代碼了。老闆卻對研發團隊的作法表示不理解:「大家太不像話了,我是付了錢的,可每次我要擴大再生產的時候,大家都要耽誤我好幾天時間,從新修改什麼代碼,若是下次我要養雞、養青蛙了呢?」
這個現代化養豬場出現的問題其實就是需求變化的問題。設計模式可使系統很容易地在某一特定的層面上適應需求的變化。使用設計模式後,系統就能夠很好的知足開閉原則:咱們不用修改原來的代碼,而只須要添加新的代碼就能夠知足新的需求了。
從這個角度來看,使用設計模式的關鍵是預測將來可能發生的需求變化,並在設計過程當中選用合適的設計模式。同時,咱們也應該將設計模式應用於系統中那些明顯不穩定、很可能發生變化的部分,而不該該爲了體驗創造的樂趣,把設計模式應用於那些永遠不會發生變化的組件中去。
例如,在養豬場系統中,當養豬場頭一回引進新品種長白豬時,咱們就敏銳當即認識到豬的種類是一種易變的因素,這時就必須引入設計模式以適應這種需求的變化。
從這裏咱們可總結出有關設計模式的第一個核心設計原則:
設計模式最根本的意圖是適應需求的變化,咱們應只對變化或者可能變化的部分使用設計模式,對於不變的部分濫用設計模式就會形成「過渡設計」。
(二) 針對接口編程,而不要針對實現編程
如今,咱們來看一下如何改進這個現代化養豬場的設計,使其能最大限度地適應需求變化。
首先,咱們應該際加一個豬的抽象接口,該接口中定義了每一類豬共有的行爲,而大白豬和長白豬則具體實現這些行爲。系統中的大白豬和長白豬知足徹底替換原則,使用時客戶不用考慮豬的具體類型,就能夠直接經過抽象的豬的接口來操做具體的大白豬和長白豬對象。
而後,咱們修改餵豬機器人的代碼,使其不考慮豬的類型,只應用抽象的豬的接口來操做全部豬的對象實例。例如,餵豬時餵豬機器人須要吼叫的再也不是"大白豬,吃!"或"長白豬,吃!"而是"豬,吃!"這種經過抽象類或抽象接口來操做對象的方式就是"針用接口編程"的方法,而此前那種經過具體的類來操做對象的方法能夠被稱爲"針對實現編程"的方法。
改進後的養豬場如圖v3-1所示。
圖v3-1
這個改進的養豬場會爲咱們帶來什麼好處呢?假設如今養豬場的老闆須要引進波中豬,他只要買來幾頭波中豬的仔豬,扔進養豬場就能夠了。餵豬機器人的代碼不須要發生任何變化,它面對每一頭豬隻要說"豬,吃!"全部類型的豬均可以愉快地進食。無論養豬場飼養的豬有多少種,餵豬機器人都會把豬喂得腰肥體壯。
添加了波中豬後的系統結構如圖v3-2所示。
圖v3-2
能夠看到,餵豬機器人徹底是針對接口進行編程的,當系統添加一個新的類型時,只須要添加新類型的代碼,而系統中原有的代碼不須要作任何的改變就能夠適應新的需求一一這徹底符合開閉原則。
V3版面向對象養豬廠的實現代碼以下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 10 namespace PigFactoryV3 11 { 12 public partial class Form1 : Form 13 { 14 /* 15 * 豬悟能的博客 16 * http://www.cnblogs.com/hackpig/ 17 * 18 建成後的養豬場生意興隆, 老闆決定進軍國際市場, 可是爲了迎合外國人口味, 引入了幾條長白豬. 19 所以,這裏豬的類型成了變化點. 20 咱們把大白豬類, 改爲豬接口, 這樣餵豬機器人的工做變成了, "豬,吃", 而不是原來的 "大白豬,吃" 21 即便老闆再增長几頭波中豬, 咱們也再也不須要修改餵豬機器人的工做()了. 只須要增長一個波中豬的類實現豬接口 22 23 24 從這裏咱們能夠總結出有關設計模式的第一個核心設計原則: 25 26 設計模式最根本的意圖是適應需求變化, 咱們應只對變化或者可能變化的部分使用設計模式,對於不變的部分濫用設計 27 模式就會形成"過分設計" 28 29 */ 30 31 32 public Form1() 33 { 34 InitializeComponent(); 35 36 feedPigRobot robot = new feedPigRobot("大潤發養豬場"); 37 robot.Attack(new dbPig(1)); 38 robot.Attack(new dbPig(2)); 39 robot.Attack(new dbPig(3)); 40 robot.Attack(new dbPig(4)); 41 robot.Attack(new dbPig(5)); 42 43 robot.Attack(new cbPig(6)); 44 robot.Attack(new cbPig(7)); 45 robot.Attack(new cbPig(8)); 46 robot.Attack(new cbPig(9)); 47 robot.Attack(new cbPig(10)); 48 49 rtbMsginfo.Text = robot.work(); 50 51 } 52 } 53 54 55 public abstract class feedPigFactory 56 { 57 private string _factoryName; 58 59 public string FactoryName 60 { 61 get { return _factoryName; } 62 set { _factoryName = value; } 63 } 64 65 } 66 67 public class feedPigRobot:feedPigFactory 68 { 69 IList<Ipig> pigList = new List<Ipig>(); 70 71 public feedPigRobot(string factoryName) 72 { 73 base.FactoryName = factoryName; 74 } 75 76 public void Attack(Ipig pig) 77 { 78 pigList.Add(pig); 79 } 80 81 public string work() 82 { 83 string msgstr = string.Empty; 84 foreach (Ipig m in pigList) 85 { 86 msgstr += m.eat()+Environment.NewLine; 87 } 88 89 return string.Format("{0}{1}{2}", 90 base.FactoryName + Environment.NewLine, 91 "餵豬機器人開始工做...." + Environment.NewLine + Environment.NewLine, 92 msgstr); 93 } 94 } 95 96 97 98 public interface Ipig 99 { 100 101 int PigIndex 102 { 103 get; 104 set; 105 } 106 107 string eat(); 108 109 } 110 111 112 public class cbPig : Ipig 113 { 114 private int _pigIndex; 115 116 public int PigIndex 117 { 118 get { return _pigIndex; } 119 set { _pigIndex = value; } 120 } 121 public cbPig(int pignum) 122 { 123 this._pigIndex = pignum; 124 } 125 126 public string eat() 127 { 128 return string.Format("{0}[{1}]開始吃.", "長白豬", _pigIndex); 129 } 130 } 131 132 133 134 public class dbPig :Ipig 135 { 136 private int _pigIndex; 137 138 public int PigIndex 139 { 140 get { return _pigIndex; } 141 set { _pigIndex = value; } 142 } 143 public dbPig(int pignum) 144 { 145 this._pigIndex = pignum; 146 } 147 148 public string eat() 149 { 150 return string.Format("{0}[{1}]開始吃.", "大白豬", _pigIndex); 151 } 152 } 153 154 }
運行結果以下:
代碼分析:
(1) feedPigRobot是餵豬機器人類
其成員函數 Attack 的參數是Ipig, 它是豬的接口
public void Attack(Ipig pig)
這個參數能夠接納 dbPig , 大白豬類的實例, 或者是 cbPig, 長白豬類的實例.
work() 成員函數 遍歷全部IPig的"豬"對象, 調用它的eat()方法. (完成對全部豬餵食的操做)
foreach (Ipig m in pigList) { msgstr += m.eat()+Environment.NewLine; }
(2) 有了豬的抽象接口(Ipig), 它抽象出了豬共有了行爲"吃", 即方法eat(). 餵豬機器人只對這個豬抽象接口餵食, 就不須要知道喂的到底是長白豬,仍是大白豬了.
養豬場的老闆還曾經提到過養雞和養青蛙。對此,咱們必須明確該需求是不是合理的需求,系統是否須要適應這一需求變化。通常說來,養豬場是不會養雞、養青蛙的,咱們不必爲此多費心思。但若是老闆故意刁難的話,咱們也不是沒有解決方案:適應這一需求變化的方法是提取出一個更通常的"動物接口"機器人徹底使用"動物接口"類來操做全部的對象。
如今,面向對象的現代化養豬場又欣欣向榮地發展起來了。但咱們不能放鬆警戒,變化的需求隨時都會出現。例如這一次,養豬場的老闆忽然以爲,老從外面引進仔豬太虧,他但願能在養豬場內部建造一個繁殖基地,自產自銷。因而,咱們建造了二個豬工廠,最初的豬工廠結構如圖V5-1所示。
圖v5-1
不難發現,在實現豬工廠時,咱們又陷入了針對實現編程的陷阱。管理員使用豬工廠來選擇繁殖哪一種類型的仔豬,豬工廠根據管理員的要求執行不一樣的繁殖過程,繁殖不一樣類型的仔豬。對於系統中己有的大白豬和長白豬,這沒有問題,可是當咱們想繁殖波中豬時,問題又產生了,豬工廠的代碼必須修改。顯然,咱們必須想辦法來隔離有關對象建立的代碼,以適應需求變化。設計模式中的建立型模式偏偏能夠知足咱們的須要。
爲了隔離具體的繁殖過程,咱們能夠定義一個豬工廠的抽象接U類,其派生類大自豬工廠和長白豬工廠具體地實現接口中的繁殖行爲。這樣,和具體實現相關的代碼被推遲到了具體的派生類工廠中去實現,咱們在系統外只要用不一樣的派生類工廠調用繁殖方法,就能夠繁殖出不一樣的仔豬了。修改後的結構如圖V6-1所示。
這一改進爲咱們帶來的好處是,當咱們要添加一種豬的類型時,也相應地添加繁殖這種豬的工廠,系統內原布的代碼不須要改變。這時,豬工廠負責繁殖仔豬,而後把仔豬交給餵豬機器人,這些仔豬的生、老、病、死就徹底由餵豬機器人來負責了。爲了在這一結構中添加波中豬,咱們須要作的事情有添加波中豬類、添加波中豬工廠類、修改管理員繁殖仔豬的代碼等,這些工做都是在系統外完成的,與系統內原有的代碼無關(如圖V6-1所示)。
圖v6-1
面向對象養豬廠V6版實現代碼以下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 10 namespace PigFactoryV6 11 { 12 public partial class Form1 : Form 13 { 14 /* 15 * 豬悟能的博客 16 * http://www.cnblogs.com/hackpig/ 17 * 18 忽然有一天,老闆以爲從外面進豬仔太虧, 他但願在養豬場內部創建一個繁殖基地, 自產自銷. 19 因而, 軟件團隊創建了一個豬工廠. 20 21 如今, 養豬場徹底能夠由自產的仔豬開始餵養了. 22 不管是增長要餵食豬的品種, 仍是繁殖新的豬的品種, 咱們如今都不用改動原有的代碼. 23 只須要增長新的豬品種的類, 實現豬工廠接口, 和豬接口就能夠了. 24 25 由些咱們總結出設計模式第二個核心設計原則: 26 27 儘可能針對接口編程, 而不要針對實現編程. 針對接口編程的組件不須要知道對象的具體 28 類型和實現, 只須要知道抽象類定義了哪些接口, 這減小了實現上的依賴關係. 29 30 */ 31 32 public Form1() 33 { 34 InitializeComponent(); 35 36 feedPigRobot robot1 = new feedPigRobot("大潤發養豬場"); 37 adminMan man1=new adminMan(2); 38 foreach (Ipig m in man1.Piglist) 39 robot1.Attack(m); 40 this.rtbMsgInfo.Text = robot1.work(); 41 42 } 43 } 44 45 46 public class adminMan 47 { 48 private IList<Ipig> _piglist; 49 50 public IList<Ipig> Piglist 51 { 52 get { return _piglist; } 53 set { _piglist = value; } 54 } 55 56 public adminMan(int breedType) 57 { 58 IList<Ipig> piglist1 = new List<Ipig>(); 59 switch (breedType) 60 { 61 case 1: //大白豬 62 foreach (Ipig m in new breedDBpig().breedPig()) 63 piglist1.Add(m); 64 break; 65 case 2: //長白豬 66 foreach (Ipig m in new breedCBpig().breedPig()) 67 piglist1.Add(m); 68 break; 69 default: 70 break; 71 } 72 if (piglist1.Count > 0) 73 _piglist = piglist1; 74 75 } 76 } 77 78 79 80 public interface IbreedPig 81 { 82 IList<Ipig> breedPig(); 83 } 84 85 public class breedDBpig : IbreedPig 86 { 87 private IList<Ipig> pigList = new List<Ipig>(); 88 public IList<Ipig> breedPig() 89 { 90 Random ran = new Random(DateTime.Now.Millisecond); 91 for (int i = 0; i < ran.Next(2, 20); i++) 92 pigList.Add(new dbPig(i)); 93 return pigList; 94 } 95 } 96 97 public class breedCBpig : IbreedPig 98 { 99 private IList<Ipig> pigList = new List<Ipig>(); 100 public IList<Ipig> breedPig() 101 { 102 Random ran = new Random(DateTime.Now.Millisecond); 103 for (int i = 0; i < ran.Next(2, 20); i++) 104 pigList.Add(new cbPig(i)); 105 return pigList; 106 } 107 } 108 109 110 public abstract class feedPigFactory 111 { 112 private string _factoryName; 113 114 public string FactoryName 115 { 116 get { return _factoryName; } 117 set { _factoryName = value; } 118 } 119 120 } 121 122 public class feedPigRobot : feedPigFactory 123 { 124 IList<Ipig> pigList = new List<Ipig>(); 125 126 public feedPigRobot(string factoryName) 127 { 128 base.FactoryName = factoryName; 129 } 130 131 public void Attack(Ipig pig) 132 { 133 pigList.Add(pig); 134 } 135 136 public string work() 137 { 138 string msgstr = string.Empty; 139 foreach (Ipig m in pigList) 140 { 141 msgstr += m.eat() + Environment.NewLine; 142 } 143 144 return string.Format("{0}{1}{2}", 145 base.FactoryName + Environment.NewLine, 146 "餵豬機器人開始工做...." + Environment.NewLine + Environment.NewLine, 147 msgstr); 148 } 149 } 150 151 152 153 public interface Ipig 154 { 155 156 int PigIndex 157 { 158 get; 159 set; 160 } 161 162 string eat(); 163 164 } 165 166 167 public class cbPig : Ipig 168 { 169 private int _pigIndex; 170 171 public int PigIndex 172 { 173 get { return _pigIndex; } 174 set { _pigIndex = value; } 175 } 176 public cbPig(int pignum) 177 { 178 this._pigIndex = pignum; 179 } 180 181 public string eat() 182 { 183 return string.Format("{0}[{1}]開始吃.", "長白豬", _pigIndex); 184 } 185 } 186 187 188 189 public class dbPig : Ipig 190 { 191 private int _pigIndex; 192 193 public int PigIndex 194 { 195 get { return _pigIndex; } 196 set { _pigIndex = value; } 197 } 198 public dbPig(int pignum) 199 { 200 this._pigIndex = pignum; 201 } 202 203 public string eat() 204 { 205 return string.Format("{0}[{1}]開始吃.", "大白豬", _pigIndex); 206 } 207 } 208 209 210 }
運行效果:
代碼說明:
(1) 下面代碼能夠看到,
feedPigRobot是餵豬機器人類, adminMan是繁殖管理類, 它使用工廠方式建立對應品種的豬.
feedPigRobot robot1 = new feedPigRobot("大潤發養豬場"); adminMan man1=new adminMan(2); foreach (Ipig m in man1.Piglist) robot1.Attack(m); this.rtbMsgInfo.Text = robot1.work();
adminMan()有個構造函數參數, 傳入參數1,或者2, 表示使用工廠方式建立隨機數量的大白豬或者是長白豬.
IList<Ipig> piglist1 = new List<Ipig>(); switch (breedType) { case 1: //大白豬 foreach (Ipig m in new breedDBpig().breedPig()) piglist1.Add(m); break; case 2: //長白豬 foreach (Ipig m in new breedCBpig().breedPig()) piglist1.Add(m); break; default: break; }
(2) 本例程傳入adminMan()參數爲2, 所以只是隨機建立了一批長白豬
由此,咱們能夠總結出設計模式的第二個核心設計原則:
儘可能針對接口編程,而不要針對實現編程。針對接口編程的組件不須要知道對象的具體類型和實現,只須要知道抽象類定義了哪些接口,這減小了實現上的依賴關係。
未完待續......
原創文章,出處 : http://www.cnblogs.com/hackpig/