步步为营 .NET 设计模式学习笔记 十、Builder(建造者模式)

 

概述 

在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法确相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?这就是要说的建造者模式。

本文通过现实汽车生产中的例子,来诠释建造者模式。

意图

将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

<Design Pattern>Builder模型图

通俗讲解:Builder模式的理解

建造者(Builder)角色:给出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体建造者(ConcreteBuilder)角色。具体建造者类必须实现这个接口所要求的方法:一个是建造方法,另一个是结果返还方法。

具体建造者(Concrete Builder)角色:担任这个角色的是于应用程序紧密相关的类,它们在应用程序调用下创建产品实例。这个角色主要完成的任务包括:

  • 实现Builder角色提供的接口,一步一步完成创建产品实例的过程。
  • 在建造过程完成后,提供产品的实例。

指导者(Director)角色:担任这个角色的类调用具体建造者角色以创建产品对象。导演者并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者对象。

产品(Product)角色:产品便是建造中的复杂对象。

指导者角色是于客户端打交道的角色。导演者角色将客户端创建产品的请求划分为对各个零件的建造请求,再将这些请求委派给具体建造者角色。具体建造者角色是做具体建造工作的,但却不为客户端所知。


简单地说,就好象我要一座房子住,可是我不知道怎么盖(简单的砌墙,层次较低),也不知道怎么样设计(建几个房间,几个门好看,层次较高), 于是我需要找一帮民工,他们会砌墙,还得找个设计师,他知道怎么设计,我还要确保民工听设计师的领导,而设计师本身也不干活,光是下命令,这里砌一堵墙,这里砌一扇门,这样民工开始建设,最后,我可以向民工要房子了。在这个过程中,设计师是什么也没有,除了他在脑子里的设计和命令,所以要房子也是跟民工要,记住了!
就象国内好多企业上erp一样,上erp,首先得找软件公司呀,找到软件公司后,软件公司说,我只知道怎么写软件,就知道怎么实现,不清楚整个erp的流程。好,那我们还得找一个咨询公司,好,找到德勤了,德勤说好,我要软件怎么做,软件公司怎么做,我就能保证软件能为你们提供erp系统了。

示例用例图:

汽车的生产其实可以看作是一个建造者模式,大众生产Audi和Satana两种轿车,我们用例设计如下:

image

代码设计:

先创建 Car.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Car
{
    private string engine;
    public string Engine
    {
        get { return engine; }
        set { engine = value; }
    }
    private string gearing;
    public string Gearing
    {
        get { return gearing; }
        set { gearing = value; }
    }
    private string brake;
    public string Brake
    {
        get { return brake; }
        set { brake = value; }
    }
 
    public string ShowInfo()
    {
        StringBuilder strBuilder = new StringBuilder();
        strBuilder.AppendLine("轿车配置如下:");
        strBuilder.AppendFormat("发动机是:{0} \n", Engine);
        strBuilder.AppendFormat("传动系是:{0} \n", Gearing);
        strBuilder.AppendFormat("制动系是:{0} \n", Brake);
        return strBuilder.ToString();
    }
 
}

然后创建BuildCar.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class BuildCar
 {
    private string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
    private Car newCar;
    public Car NewCar
    {
        get { return newCar; }
        set { newCar = value; }
    }
 
    public BuildCar()
    {
        newCar = new Car();
    }
    public abstract void BuildEngine();
    public abstract void BuildGearing();
    public abstract void BuildBrake();
 }

再创建Audi.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  class Audi:BuildCar
 {
     public override void BuildEngine()
     {
         NewCar.Engine = "V型12缸发动机";
     }
 
     public override void BuildGearing()
     {
         NewCar.Gearing = "四轮驱动(4WD)";
     }
 
     public override void BuildBrake()
     {
         NewCar.Brake = "电磁式";
     }
     public Audi()
     {
         Name = "Audi A8";
     }
 }

再创建Satana.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public   class Satana:BuildCar
 {
     public override void BuildEngine()
     {
         NewCar.Engine = "W型12缸发动机";
     }
 
     public override void BuildGearing()
     {
         NewCar.Gearing ="前置后驱(FR)";
     }
 
     public override void BuildBrake()
     {
         NewCar.Brake = "组合式制动系统";
     }
     public Satana()
     {
         Name = "SATANA";
     }
 }

再创建GenerateCar.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class GenerateCar
 {
    private BuildCar buildCar;
    public BuildCar BuildNewCar
    {
        get { return buildCar; }
        set { buildCar = value; }
    }
    public string Generate()
    {
        StringBuilder strBuider = new StringBuilder();
        strBuider.AppendFormat("开始组装{0}轿车. \n",buildCar.Name);
        BuildNewCar.BuildEngine();
        BuildNewCar.BuildGearing();
        BuildNewCar.BuildBrake();
        strBuider.AppendFormat("{0}轿车组装完毕. \n",buildCar.Name);
        return strBuider.ToString();
    }
    public GenerateCar(BuildCar newCar)
    {
        buildCar = newCar;
    }
 }

最后再调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public partial class Run : Form
   {
       public Run()
       {
           InitializeComponent();
       
     private void btnRun_Click(object sender, EventArgs e)
       {
          GenerateCar newCar = new GenerateCar(new Satana());
           rtbResult.AppendText(newCar.Generate() + "\n");
           rtbResult.AppendText(newCar.BuildNewCar.NewCar.ShowInfo() + "\n");
           newCar = new GenerateCar(new Audi());
           rtbResult.AppendText(newCar.Generate() + "\n");
           rtbResult.AppendText(newCar.BuildNewCar.NewCar.ShowInfo() + "\n");
       }
   }

结果如下图:

image

实现要点

1、建造者模式主要用于“分步骤构建一个复杂的对象”,在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。

2、产品不需要抽象类,特别是由于创建对象的算法复杂而导致使用此模式的情况下或者此模式应用于产品的生成过程,其最终结果可能差异很大,不大可能提炼出一个抽象产品类。
3、创建者中的创建子部件的接口方法不是抽象方法而是空方法,不进行任何操作,具体的创建者只需要覆盖需要的方法就可以,但是这也不是绝对的,特别是类似文本转换这种情况下,缺省的方法将输入原封不动的输出是合理的缺省操作。

4、前面我们说过的抽象工厂模式(Abtract Factory)解决“系列对象”的需求变化,Builder模式解决“对象部分”的需求变化,建造者模式常和组合模式(Composite Pattern)结合使用。

5、对象的构建过程由指导者完成,具体的组成由具体建造者完成,表示与构建分离。

6、 建造者和指导者是建造者模式的关键点,如果进行合并或省略就可能会转变到模版方法模式。

7、如果对象的建造步骤是简单的,并且产品拥有一致的接口可以转而使用工厂模式。

效果

1、建造者模式的使用使得产品的内部表象可以独立的变化。使用建造者模式可以使客户端不必知道产品内部组成的细节。
2、每一个Builder都相对独立,而与其它的Builder无关。
3、可使对构造过程更加精细控制。

4、将构建代码和表示代码分开。

5、建造者模式的缺点在于难于应付“分步骤构建算法”的需求变动。

适用性

以下情况应当使用建造者模式:

1、需要生成的产品对象有复杂的内部结构。
2、需要生成的产品对象的属性相互依赖,建造者模式可以强迫生成顺序。
3、 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。

4 、从代码角度来说, 如果你希望分离复杂类型构建规则和类型内部组成,或者希望把相同的构建过程用于构建不同类型的时候可以考虑使用建造者模式。

5、从应用角度来说, 如果你希望解耦产品的创建过程和产品的具体配件,或者你希望为所有产品的创建复用一套稳定并且复杂的逻辑的时候可以考虑使用建造者模式。

总结

1、建造者模式的实质是解耦组装过程和创建具体部件,使得我们不用去关心每个部件是如何组装的。

2、返回产品的方法是否必须,是否一定要在抽象建造者中有接口根据实际情况而定。如果它们有统一的接口可以在抽象建造者中体现这个抽象方法,如果没有统一的接口(比如,生产毫无关联的产品)则可以在具体建造者中各自实现这个方法,如果创建的产品是一种产品,那么甚至可以省略返回产品的接口(本文的例子就是这样)。

posted @   spring yang  阅读(2600)  评论(8编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示