(第Ⅱ部分 创建型模式篇) 第3章 建造者模式(Builder Pattern)

    技术2025-05-19  44

    概述 在 软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧 烈的变化,但是将它们组合在一起的算法确相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的 “稳定构建算法”不随着需求改变而改变?这就是要说的建造者模式。 本文通过现实生活中的买 KFC 的例子,用图解的方式来诠释建造者模式。 意图 将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 模型图 生活中的例子 生 成器模式将复杂对象的构建与对象的表现分离开来,这样使得同样的构建过程可以创建出不同的表现。这种模式用于快餐店制作儿童餐。典型的儿童餐包括一个主 食,一个辅食,一杯饮料和一个玩具(例如汉堡、炸鸡、可乐和玩具车)。这些在不同的儿童餐中可以是不同的,但是组合成儿童餐的过程是相同的。无论顾客点的 是汉堡,三名治还是鸡肉,过程都是一样的。柜台的员工直接把主食,辅食和玩具放在一起。这些是放在一个袋子中的。饮料被倒入杯中,放在袋子外边。这些过程 在相互竞争的餐馆中是同样的。 实现过程图解 在这里我们还是以去 KFC 店买套餐为例子,示意图如下: 客户端:顾客。想去买一套套餐(这里面包括汉堡,可乐,薯条),可以有 1 号和 2 号两种套餐供顾客选择。 指导者角色:收银员。知道顾客想要买什么样的套餐,并告诉餐馆员工去准备套餐。 建造者角色:餐馆员工。按照收银员的要求去准备具体的套餐,分别放入汉堡,可乐,薯条等。 产品角色:最后的套餐,所有的东西放在同一个盘子里面。 下面开始我们的买套餐过程。 1 .客户创建 Derector 对象,并用它所想要的 Builder 对象进行配置。顾客进入 KFC 店要买套餐,先找到一个收银员,相当于创建了一个指导者对象。这位收银员给出两种套餐供顾客选择: 1 普通套餐, 2 黄金套餐。完成的工作如时序图中红色部分所示。 程序实现:  1 using  System;  2 using  System.Configuration;  3 using  System.Reflection;  4  5 namespace  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

     

    产品(套餐)类: . 指导者通知建造器。收银员(指导者)告知餐馆员工准备套餐。这里我们准备套餐的顺序是:放入汉堡,可乐倒入杯中,薯条放入盒中,并把这些东西都放在盘子 上。这个过程对于普通套餐和黄金套餐来说都是一样的,不同的是它们的汉堡,可乐,薯条价格不同而已。如时序图红色部分所示:  1 using  System;  2 using  System.Collections;  3  4 namespace  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 程序实现:  1 using  System;  2  3 namespace  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 .建造者处理指导者的要求,并将部件添加到产品中。餐馆员工(建造者)按照收银员要求的把对应的汉堡,可乐,薯条放入盘子中。这部分是建造者模式里面富于变化的部分,因为顾客选择的套餐不同,套餐的组装过程也不同,这步完成产品对象的创建工作。 程序实现:  1 using  System;  2  3 namespace  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

     

     1 using  System;  2  3 namespace  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

     

     1 using  System;  2  3 namespace  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 .客户从建造者检索产品。从餐馆员工准备好套餐后,顾客再从餐馆员工那儿拿回套餐。这步客户程序要做的仅仅是取回已经生成的产品对象,如时序图中红色部分所示。 完整的客户程序:  1 using  System;  2 using  System.Configuration;  3 using  System.Reflection;  4  5 namespace  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 // 关于建造房屋的例子   2 using  System;   3 using  System.Collections;   4   5 ///  <summary>   6 ///  抽象建造者   7 ///  </summary>   8 public   interface  IHouse   9 {  10      bool  GetBackyard();  11      long  NoOfRooms();  12      string   Description();  13 }  14  15 ///  <summary>  16 ///  具体建造者  17 ///  </summary>  18 public   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>  66 public   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 121 public   interface  IRoom 122 { 123      string  RoomName { get ; set ;} 124 } 125 126 public   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> 145 public   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> 163 public   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、   …… 总结 建造者模式的实质是解耦组装过程和创建具体部件,使得我们不用去关心每个部件是如何组装的。 ______________________________________________________________________________________ 源程序下载:/Files/Terrylee/BuilderPattern.rar 参考资料: Java 与设计模式》阎宏 《设计模式(中文版)》 DesignPatternsExplained

     

    最新回复(0)