概述
在軟件系統中,有時候面臨著“一個復雜對象”的創建工作,其通常由各個部分的子對象用一定的算法構成;由於需求的變化,這個復雜對象的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的算法確相對穩定。如何應對這種變化?如何提供一種“封裝機制”來隔離出“復雜對象的各個部分”的變化,從而保持系統中的“穩定構建算法”不隨著需求改變而改變?這就是要說的建造者模式。
意圖
將一個復雜的構建與其表示相分離,使得同樣的構建過程可以創建不同的表示。
模型圖
生活中的例子
生成器模式將復雜對象的構建與對象的表現分離開來,這樣使得同樣的構建過程可以創建出不同的表現。這種模式用於快餐店制作兒童餐。典型的兒童餐包括一個主食,一個輔食,一杯飲料和一個玩具(例如漢堡、炸雞、可樂和玩具車)。這些在不同的兒童餐中可以是不同的,但是組合成兒童餐的過程是相同的。無論顧客點的是漢堡,三名治還是雞肉,過程都是一樣的。櫃台的員工直接把主食,輔食和玩具放在一起。這些是放在一個袋子中的。飲料被倒入杯中,放在袋子外邊。這些過程在相互競爭的餐館中是同樣的。
實現過程圖解
在這裡我們還是以去KFC店買套餐為例子,示意圖如下:
客戶端:顧客。想去買一套套餐(這裡面包括漢堡,可樂,薯條),可以有1號和2號兩種套餐供顧客選擇。
指導者角色:收銀員。知道顧客想要買什麼樣的套餐,並告訴餐館員工去准備套餐。
建造者角色:餐館員工。按照收銀員的要求去准備具體的套餐,分別放入漢堡,可樂,薯條等。
產品角色:最後的套餐,所有的東西放在同一個盤子裡面。
下面開始我們的買套餐過程。
1.客戶創建Derector對象,並用它所想要的Builder對象進行配置。顧客進入KFC店要買套餐,先找到一個收銀員,相當於創建了一個指導者對象。這位收銀員給出兩種套餐供顧客選擇:1普通套餐,2黃金套餐。完成的工作如時序圖中紅色部分所示。
程序實現:
1using System;
2using System.Configuration;
3using System.Reflection;
4
5namespace KFC
6{
7 /**//// <summary>
8 /// Client 類
9 /// </summary>
10 public class Client
11 {
12 public static void Main(string[] args)
13 {
14 FoodManager foodmanager = new FoodManager();
15
16 Builder instance;
17
18 Console.WriteLine("Please Enter Food No:");
19
20 string No = Console.ReadLine();
21
22 string foodType = ConfigurationSettings.AppSettings["No"+No];
23
24 instance = (Builder)Assembly.Load("KFC").CreateInstance("KFC." + foodType);
25
26 foodmanager.Construct(instance);
27 }
28 }
29}
30
產品(套餐)類:
1using System; 2using System.Collections; 3 4namespace KFC 5{ 6 /**//// <summary> 7 /// Food類,即產品類 8 /// </summary> 9 public class Food 10 { 11 Hashtable food = new Hashtable(); 12 13 /**//// <summary> 14 /// 添加食品 15 /// </summary> 16 /// <param name="strName">食品名稱</param> 17 /// <param name="Price">價格</param> 18 public void Add(string strName,string Price) 19 { 20 food.Add(strName,Price); 21 } 22 23 /**//// <summary> 24 /// 顯示食品清單 25 /// </summary> 26 public void Show() 27 { 28 IDictionaryEnumerator myEnumerator = food.GetEnumerator(); 29 Console.WriteLine("Food List:"); 30 Console.WriteLine("------------------------------"); 31 string strfoodlist = ""; 32 while(myEnumerator.MoveNext()) 33 { 34 strfoodlist = strfoodlist + "\n\n" + myEnumerator.Key.ToString(); 35 strfoodlist = strfoodlist + ":\t" +myEnumerator.Value.ToString(); 36 } 37 Console.WriteLine(strfoodlist); 38 Console.WriteLine("\n------------------------------"); 39 } 40 } 41} 42
2.指導者通知建造器。收銀員(指導者)告知餐館員工准備套餐。這裡我們准備套餐的順序是:放入漢堡,可樂倒入杯中,薯條放入盒中,並把這些東西都放在盤子上。這個過程對於普通套餐和黃金套餐來說都是一樣的,不同的是它們的漢堡,可樂,薯條價格不同而已。如時序圖紅色部分所示:
程序實現:
1using System;
2
3namespace KFC
4{
5 /**//// <summary>
6 /// FoodManager類,即指導者
7 /// </summary>
8 public class FoodManager
9 {
10 public void Construct(Builder builder)
11 {
12 builder.BuildHamb();
13
14 builder.BuildCoke();
15
16 builder.BuildChip();
17 }
18 }
19}
20
3.建造者處理指導者的要求,並將部件添加到產品中。餐館員工(建造者)按照收銀員要求的把對應的漢堡,可樂,薯條放入盤子中。這部分是建造者模式裡面富於變化的部分,因為顧客選擇的套餐不同,套餐的組裝過程也不同,這步完成產品對象的創建工作。
程序實現:
1using System;
2
3namespace KFC
4{
5 /**//// <summary>
6 /// Builder類,即抽象建造者類,構造套餐
7 /// </summary>
8 public abstract class Builder
9 {
10 /**//// <summary>
11 /// 添加漢堡
12 /// </summary>
13 public abstract void BuildHamb();
14
15 /**//// <summary>
16 /// 添加可樂
17 /// </summary>
18 public abstract void BuildCoke();
19
20 /**//// <summary>
21 /// 添加薯條
22 /// </summary>
23 public abstract void BuildChip();
24
25 /**//// <summary>
26 /// 返回結果
27 /// </summary>
28 /// <returns>食品對象</returns>
29 public abstract Food GetFood();
30 }
31}
32
1using System;
2
3namespace KFC
4{
5 /**//// <summary>
6 /// NormalBuilder類,具體構造者,普通套餐
7 /// </summary>
8 public class NormalBuilder:Builder
9 {
10 private Food NormalFood = new Food();
11
12 public override void BuildHamb()
13 {
14 NormalFood.Add("NormalHamb","¥10.50");
15 }
16
17 public override void BuildCoke()
18 {
19 NormalFood.Add("CokeCole","¥4.50");
20 }
21
22 public override void BuildChip()
23 {
24 NormalFood.Add("FireChips","¥2.00");
25 }
26
27 public override Food GetFood()
28 {
29 return NormalFood;
30 }
31
32 }
33}
34
1using System;
2
3namespace KFC
4{
5 /**//// <summary>
6 /// GoldBuilder類,具體構造者,黃金套餐
7 /// </summary>
8 public class GoldBuilder:Builder
9 {
10 private Food GoldFood = new Food();
11
12 public override void BuildHamb()
13 {
14 GoldFood.Add("GoldHamb","¥13.50");
15 }
16
17 public override void BuildCoke()
18 {
19 GoldFood.Add("CokeCole","¥4.50");
20 }
21
22 public override void BuildChip()
23 {
24 GoldFood.Add("FireChips","¥3.50");
25 }
26
27 public override Food GetFood()
28 {
29 return GoldFood;
30 }
31
32 }
33}
34
4.客戶從建造者檢索產品。從餐館員工准備好套餐後,顧客再從餐館員工那兒拿回套餐。這步客戶程序要做的僅僅是取回已經生成的產品對象,如時序圖中紅色部分所示。
完整的客戶程序:
1using System;
2using System.Configuration;
3using System.Reflection;
4
5namespace KFC
6{
7 /**//// <summary>
8 /// Client 類
9 /// </summary>
10 public class Client
11 {
12 public static void Main(string[] args)
13 {
14 FoodManager foodmanager = new FoodManager();
15
16 Builder instance;
17
18 Console.WriteLine("Please Enter Food No:");
19
20 string No = Console.ReadLine();
21
22 string foodType = ConfigurationSettings.AppSettings["No"+No];
23
24 instance = (Builder)Assembly.Load("KFC").CreateInstance("KFC." + foodType);
25
26 foodmanager.Construct(instance);
27
28 Food food = instance.GetFood();
29 food.Show();
30
31 Console.ReadLine();
32 }
33 }
34}
35
通過分析不難看出,在這個例子中,在准備套餐的過程是穩定的,即按照一定的步驟去做,而套餐的組成部分則是變化的,有可能是普通套餐或黃金套餐等。這個變化就是建造者模式中的“變化點“,就是我們要封裝的部分。
另外一個例子
在這裡我們再給出另外一個關於建造房子的例子。客戶程序通過調用指導者 (CDirector class)的BuildHouse()方法來創建一個房子。該方法有一個布爾型的參數blnBackyard,當blnBackyard為假時指導者將創建一個Apartment(Concrete Builder),當它為真時將創建一個Single Family Home(Concrete Builder)。這兩種房子都實現了接口Ihouse。
程序實現:
1//關於建造房屋的例子 2using System; 3using System.Collections; 4 5/**//// <summary> 6/// 抽象建造者 7/// </summary> 8public interface IHouse 9{ 10 bool GetBackyard(); 11 long NoOfRooms(); 12 string Description(); 13} 14 15/**//// <summary> 16/// 具體建造者 17/// </summary> 18public class CApt:IHouse 19{ 20 private bool mblnBackyard; 21 private Hashtable Rooms; 22 public CApt() 23 { 24 CRoom room; 25 Rooms = new Hashtable(); 26 room = new CRoom(); 27 room.RoomName = "Master Bedroom"; 28 Rooms.Add ("room1",room); 29 30 room = new CRoom(); 31 room.RoomName = "Second Bedroom"; 32 Rooms.Add ("room2",room); 33 34 room = new CRoom(); 35 room.RoomName = "Living Room"; 36 Rooms.Add ("room3",room); 37 38 mblnBackyard = false; 39 } 40 41 public bool GetBackyard() 42 { 43 return mblnBackyard; 44 } 45 public long NoOfRooms() 46 { 47 return Rooms.Count; 48 } 49 public string Description() 50 { 51 IDictionaryEnumerator myEnumerator = Rooms.GetEnumerator(); 52 string strDescription; 53 strDescription = "This is an Apartment with " + Rooms.Count + " Rooms \n"; 54 strDescription = strDescription + "This Apartment doesn't have a backyard \n"; 55 while (myEnumerator.MoveNext()) 56 { 57 strDescription = strDescription + "\n" + myEnumerator.Key + "\t" + ((CRoom)myEnumerator.Value).RoomName; 58 } 59 return strDescription; 60 } 61} 62 63/**//// <summary> 64/// 具體建造者 65/// </summary> 66public class CSFH:IHouse 67{ 68 private bool mblnBackyard; 69 private Hashtable Rooms; 70 public CSFH() 71 { 72 CRoom room; 73 Rooms = new Hashtable(); 74 75 room = new CRoom(); 76 room.RoomName = "Master Bedroom"; 77 Rooms.Add ("room1",room); 78 79 room = new CRoom(); 80 room.RoomName = "Second Bedroom"; 81 Rooms.Add ("room2",room); 82 83 room = new CRoom(); 84 room.RoomName = "Third Room"; 85 Rooms.Add ("room3",room); 86 87 room = new CRoom(); 88 room.RoomName = "Living Room"; 89 Rooms.Add ("room4",room); 90 91 room = new CRoom(); 92 room.RoomName = "Guest Room"; 93 Rooms.Add ("room5",room); 94 95 mblnBackyard = true; 96 97 } 98 99 public bool GetBackyard() 100 { 101 return mblnBackyard; 102 } 103 public long NoOfRooms() 104 { 105 return Rooms.Count; 106 } 107 public string Description() 108 { 109 IDictionaryEnumerator myEnumerator = Rooms.GetEnumerator(); 110 string strDescription; 111 strDescription = "This is an Single Family Home with " + Rooms.Count + " Rooms \n"; 112 strDescription = strDescription + "This house has a backyard \n"; 113 while (myEnumerator.MoveNext()) 114 { 115 strDescription = strDescription + "\n" + myEnumerator.Key + "\t" + ((CRoom)myEnumerator.Value).RoomName; 116 } 117 return strDescription; 118 } 119} 120 121public interface IRoom 122{ 123 string RoomName{get;set;} 124} 125 126public class CRoom:IRoom 127{ 128 private string mstrRoomName; 129 public string RoomName 130 { 131 get 132 { 133 return mstrRoomName; 134 } 135 set 136 { 137 mstrRoomName = value; 138 } 139 } 140} 141 142/**//// <summary> 143/// 指導者 144/// </summary> 145public class CDirector 146{ 147 public IHouse BuildHouse(bool blnBackyard) 148 { 149 if (blnBackyard) 150 { 151 return new CSFH(); 152 } 153 else 154 { 155 return new CApt(); 156 } 157 } 158} 159 160/**//// <summary> 161/// 客戶程序 162/// </summary> 163public class Client 164{ 165 static void Main(string[] args) 166 { 167 CDirector objDirector = new CDirector(); 168 IHouse objHouse; 169 170 string Input = Console.ReadLine(); 171 objHouse = objDirector.BuildHouse(bool.Parse(Input)); 172 173 Console.WriteLine(objHouse.Description()); 174 Console.ReadLine(); 175 } 176} 177 178
建造者模式的幾種演化
省略抽象建造者角色
系統中只需要一個具體建造者,省略掉抽象建造者,結構圖如下:
指導者代碼如下:
1 class Director
2 {
3 private ConcreteBuilder builder;
4
5 public void Construct()
6 {
7 builder.BuildPartA();
8 builder.BuildPartB();
9 }
10 }
省略指導者角色
抽象建造者角色已經被省略掉,還可以省略掉指導者角色。讓Builder角色自己扮演指導者與建造者雙重角色。結構圖如下:
建造者角色代碼如下:
1 public class Builder
2 {
3 private Product product = new Product();
4
5 public void BuildPartA()
6 {
7 //
8 }
9
10 public void BuildPartB()
11 {
12 //
13 }
14
15 public Product GetResult()
16 {
17 return product;
18 }
19
20 public void Construct()
21 {
22 BuildPartA();
23 BuildPartB();
24 }
25 }
客戶程序:
1 public class Client
2 {
3 private static Builder builder;
4
5 public static void Main()
6 {
7 builder = new Builder();
8 builder.Construct();
9 Product product = builder.GetResult();
10 }
11 }
合並建造者角色和產品角色
建造模式失去抽象建造者角色和指導者角色後,可以進一步退化,從而失去具體建造者角色,此時具體建造者角色和產品角色合並,從而使得產品自己就是自己的建造者。這樣做混淆了對象的建造者和對象本身,但是有時候一個產品對象有著固定的幾個零件,而且永遠只有這幾個零件,此時將產品類和建造類合並,可以使系統簡單易讀。結構圖如下:
實現要點
1、建造者模式主要用於“分步驟構建一個復雜的對象”,在這其中“分步驟”是一個穩定的算法,而復雜對象的各個部分則經常變化。
2、產品不需要抽象類,特別是由於創建對象的算法復雜而導致使用此模式的情況下或者此模式應用於產品的生成過程,其最終結果可能差異很大,不大可能提煉出一個抽象產品類。
3、創建者中的創建子部件的接口方法不是抽象方法而是空方法,不進行任何操作,具體的創建者只需要覆蓋需要的方法就可以,但是這也不是絕對的,特別是類似文本轉換這種情況下,缺省的方法將輸入原封不動的輸出是合理的缺省操作。
4、前面我們說過的抽象工廠模式(Abtract Factory)解決“系列對象”的需求變化,Builder模式解決“對象部分”的需求變化,建造者模式常和組合模式(Composite Pattern)結合使用。
效果
1、建造者模式的使用使得產品的內部表象可以獨立的變化。使用建造者模式可以使客戶端不必知道產品內部組成的細節。
2、每一個Builder都相對獨立,而與其它的Builder無關。
3、可使對構造過程更加精細控制。
4、將構建代碼和表示代碼分開。
5、建造者模式的缺點在於難於應付“分步驟構建算法”的需求變動。
適用性
以下情況應當使用建造者模式:
1、需要生成的產品對象有復雜的內部結構。
2、需要生成的產品對象的屬性相互依賴,建造者模式可以強迫生成順序。
3、在對象創建過程中會使用到系統中的一些其它對象,這些對象在產品對象的創建過程中不易得到。
應用場景
1、RTF文檔交換格式閱讀器。
2、.NET環境下的字符串處理StringBuilder,這是一種簡化了的建造者模式。
3、……
總結
建造者模式的實質是解耦組裝過程和創建具體部件,使得我們不用去關心每個部件是如何組裝的。
本文配套源碼