(三) 優先使用聚合,而不是繼承編程
有一段時間,養豬場的老闆僱用了清潔工人來打掃豬舍。但有一天,老闆突然對本身說"不對啊,既然我有機器人,爲何還要僱人來作這件事情?應該讓機器人來打掃宿舍!"
因而,這個需求被提交到了機器人的研發小組。看到這個需求,咱們敏感地意識到,這是一個潛藏了更多變化的需求,將來機器人的功能還可能會不斷增長,因而,咱們提取出了一個抽象的機器人接口,並實現了兩個具體的機器人類一-餵豬機器人和清潔機器人。系統的結構如圖V8-1所示。設計模式
圖V8-1this
這樣一來,老闆但願機器人工做時,能夠調用機器人接口的"工做"方法。因爲這也是針對接口編程,當老闆須要新的機器人時,只要添加具體的機器人類便可,其餘代碼無需變化。這彷佛己經解決了咱們遇到的問題。
但這裏還存在另一個問題:圖v8-1 中的繼承結構是在編譯期間就肯定了的,在運行期不能發生任何變化。所以,若是養豬場須要一個餵豬機器人和→個清潔機器人,那麼咱們必須在養豬場中放進這兩個具體的機器人。依此類推,若是將來養豬場還須要獸醫機器人、屠宰機器人等等,養豬場中不就擠滿了機器人嗎?更爲重要的是,每添加一種機器人的類型,主是們就必須改動代碼中的某一個地方,以便把這個機器人放進養豬場中,這就又會違反開閉原則了。在這種狀況下,使用聚合的機制能很好地解決問題,由於基於聚合的結構能夠在運行期間發生變化。
使用聚合機制的養豬場如圖v10-1 所示。咱們把機器人接口改爲了功能接口,而清潔功能和餵豬功能實現了這個功能接口。真正的機器人類中聚合了一個功能接口的引用,這樣,咱們只須要在養豬場中放進一個機器人,該機器人中聚合了一個餵豬功能,這時它是一個餵豬機器人。當咱們須要打掃養豬場時,老闆只須要調用機器人中的"變形"方法,並傳遞一個"清潔功能"對象給機器人,機器人就會像《變形金剛》中的"擎天柱"同樣,大吼一聲"汽車人,變形"就變成了-個清潔機器人了。spa
圖v10-1設計
面向對象養豬廠V10版本實現代碼以下:3d
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 PigFactoryV10 11 { 12 /* 13 * 豬悟能的博客 14 * http://www.cnblogs.com/hackpig/ 15 * 16 有一段時間,老闆僱用清潔工人打掃豬廠,但有一天,老闆對本身說「我有了機器人,爲何還要僱用人打掃豬場?」 17 因而,軟件團隊必須開發新的清潔機器人品種 18 這裏,機器人的類型成了變化點 19 20 下面的代碼使用了聚合方式,把機器人功能變成接口,只要操做員調用「變形」功能,並傳入一個「清潔功能」,機器人就會 21 由餵豬機器人變身爲清潔機器人。 22 若是將來要增長屠宰機器人,原有代碼也不用修改。 23 24 這裏反映出設計模式的第三個核心設計原則: 25 26 優先使用聚合而不是繼承。 27 */ 28 29 public partial class Form1 : Form 30 { 31 public Form1() 32 { 33 InitializeComponent(); 34 btn_clean.Click += new EventHandler(btn_clean_Click); 35 btn_feed.Click += new EventHandler(btn_feed_Click); 36 } 37 38 void btn_feed_Click(object sender, EventArgs e) 39 { 40 adminMan man1 = new adminMan(1, "大潤發養豬場"); 41 this.rtbMsgInfo.Text = man1.Msg; 42 } 43 44 void btn_clean_Click(object sender, EventArgs e) 45 { 46 adminMan man1 = new adminMan(2, "大潤發養豬場"); 47 this.rtbMsgInfo.Text = man1.Msg; 48 } 49 } 50 51 52 public class adminMan:feedPigFactory 53 { 54 private string _msg; 55 56 public string Msg 57 { 58 get { return _msg; } 59 set { _msg = value; } 60 } 61 62 public adminMan(int funId,string factoryname) 63 { 64 base.FactoryName = factoryname; 65 robot robot1 = null; 66 switch (funId) 67 { 68 case 1: //餵食 69 robot1= new robot(); 70 IList<Ipig> list1=new List<Ipig>(); 71 list1.Add(new dbPig(1)); 72 list1.Add(new dbPig(2)); 73 list1.Add(new dbPig(3)); 74 robot1.transformation(new feedPig(list1)); 75 this._msg = robot1.work(); 76 break; 77 case 2: //清潔 78 robot1= new robot(); 79 robot1.transformation(new clean()); 80 this._msg= robot1.work(); 81 break; 82 default: 83 break; 84 } 85 } 86 87 88 } 89 90 91 public interface IfunInterface 92 { 93 string work(); 94 } 95 96 public class robot 97 { 98 private IfunInterface _robotFun; 99 100 public IfunInterface RobotFun 101 { 102 get { return _robotFun; } 103 set { _robotFun = value; } 104 } 105 106 public void transformation(IfunInterface robotFun) 107 { 108 this._robotFun = robotFun; 109 } 110 public string work() 111 { 112 return this._robotFun.work(); 113 } 114 } 115 116 117 118 119 120 public class clean : IfunInterface 121 { 122 public string work() 123 { 124 return "正在打掃清潔..."+Environment.NewLine; 125 } 126 } 127 128 public class feedPig : IfunInterface 129 { 130 IList<Ipig> pigList = new List<Ipig>(); 131 132 public feedPig(IList<Ipig> plist) 133 { 134 foreach (Ipig m in plist) 135 pigList.Add(m); 136 } 137 138 public feedPig() 139 { 140 } 141 142 public void Attack(Ipig pig) 143 { 144 pigList.Add(pig); 145 } 146 147 public string work() 148 { 149 string msgstr = string.Empty; 150 foreach (Ipig m in pigList) 151 { 152 msgstr += m.eat() + Environment.NewLine; 153 } 154 155 return string.Format("{0}{1}{2}", 156 "大潤發養豬場" + Environment.NewLine, 157 "餵豬機器人開始工做...." + Environment.NewLine + Environment.NewLine, 158 msgstr); 159 } 160 } 161 162 163 164 public abstract class feedPigFactory 165 { 166 private string _factoryName; 167 168 public string FactoryName 169 { 170 get { return _factoryName; } 171 set { _factoryName = value; } 172 } 173 174 } 175 176 177 178 public interface Ipig 179 { 180 181 int PigIndex 182 { 183 get; 184 set; 185 } 186 187 string eat(); 188 189 } 190 191 192 public class cbPig : Ipig 193 { 194 private int _pigIndex; 195 196 public int PigIndex 197 { 198 get { return _pigIndex; } 199 set { _pigIndex = value; } 200 } 201 public cbPig(int pignum) 202 { 203 this._pigIndex = pignum; 204 } 205 206 public string eat() 207 { 208 return string.Format("{0}[{1}]開始吃.", "長白豬", _pigIndex); 209 } 210 } 211 212 213 214 public class dbPig : Ipig 215 { 216 private int _pigIndex; 217 218 public int PigIndex 219 { 220 get { return _pigIndex; } 221 set { _pigIndex = value; } 222 } 223 public dbPig(int pignum) 224 { 225 this._pigIndex = pignum; 226 } 227 228 public string eat() 229 { 230 return string.Format("{0}[{1}]開始吃.", "大白豬", _pigIndex); 231 } 232 } 233 234 235 236 }
運行結果如上圖所示, 老闆能夠下達指令在餵食和清潔機器人之間切換了.指針
代碼說明:code
在這裏,餵豬機器人類是把原來直接調用Work() 餵食方法, 變成了先由transformation()指定功能類型, 再來執行Work().orm
public class robot { private IfunInterface _robotFun; public IfunInterface RobotFun { get { return _robotFun; } set { _robotFun = value; } } public void transformation(IfunInterface robotFun) { this._robotFun = robotFun; } public string work() { return this._robotFun.work(); } }
而功能類型就是個IfunInterface接口, 而clearn(打掃清潔功能), feedPig(餵豬功能), 都是承繼這個接口的.對象
public interface IfunInterface { string work(); }
public class clean : IfunInterface public class feedPig : IfunInterface
最後利用工廠方法, 決定了機器人在餵食,仍是清潔兩種功能之間切換.
public adminMan(int funId,string factoryname) { base.FactoryName = factoryname; robot robot1 = null; switch (funId) { case 1: //餵食 robot1= new robot(); IList<Ipig> list1=new List<Ipig>(); list1.Add(new dbPig(1)); list1.Add(new dbPig(2)); list1.Add(new dbPig(3)); robot1.transformation(new feedPig(list1)); this._msg = robot1.work(); break; case 2: //清潔 robot1= new robot(); robot1.transformation(new clean()); this._msg= robot1.work(); break; default: break; } }
實際上咱們是聚合IfunInterface這個抽象接口,即經過指向接口類的引用來訪問對象, 這種實現方法實際上是綜合了聚合與繼承兩種機制的方式
此後,當咱們添加一個新的機器人種類(如獸醫機器人)時,只須要添加一個獸醫功能的派生類,老闆就能夠根據本身的須要,在任什麼時候刻命令機器人在三個種類之間隨意變形。能夠看出,添加一個機器人類型時,須要改動的代碼都在系統外部,系統內已有的代碼不須要發生變化。這裏的聚合機制使咱們很好地知足了開閉原則。
總之,繼承和聚合是兩種各不相同也各有優缺點的機制:
人員來講比較容易理解。但繼承也有缺點:
首先,你不能在運行期間改變繼承樹的結構,由於繼承是在編譯期間定義的:
其次,基類中每每定義了部分的實現,基類的實現暴露給派生類後,繼承機制就會破壞數據和操做的封裝,使派生類對基類產生較強的依賴O
從上面的分析能夠看出,聚合在某些方面比繼承更爲優越。但咱們強調聚合的做用毫不是否認繼承的優勢。使用聚合時,咱們必須遵循針對接口編程的設計原則,不能聚合某一個具體的派生類對象,而應該聚合該類的抽象接口,即經過指向接口類的引用或指針來訪問對象----這種實現方法實際上是綜合了聚合與繼承兩種機制的方式。
由此,咱們能夠總結出設計模式的第三個核心設計原則
繼承反映的是類之間的"……是一個…"的關係,聚合反映的是類之間"…有一個……"或包含一個……"的關係。在不違反這個關係前提下,應該
優先使用聚合而不是繼承, 同時,聚合也必須和接口及相關的繼承結構協同使用。
全文完.
包括面向對象養豬廠的各類版本實現代碼(C#示例), 和VS2010繪製的UML類圖.
原創文章,出處 : http://www.cnblogs.com/hackpig/