# C#设计模式(5)——建造者模式(Builder Pattern)
## 一、引言
在软件系统中,有时需要创建一个复杂对象,并且这个复杂对象由其各部分子对象通过一定的步骤组合而成。例如一个采购系统中,如果需要采购员去采购一批电脑时,在这个实际需求中,电脑就是一个复杂的对象,它是由CPU、主板、硬盘、显卡、机箱等组装而成的,如果此时让采购员一台一台电脑去组装的话真是要累死采购员了,这里就可以采用建造者模式来解决这个问题,我们可以把电脑的各个组件的组装过程封装到一个建造者类对象里,建造者只要负责返还给客户端全部组件都建造完毕的产品对象就可以了。然而现实生活中也是如此的,如果公司要采购一批电脑,此时采购员不可能自己去买各个组件并把它们组织起来,此时采购员只需要像电脑城的老板说自己要采购什么样的电脑就可以了,电脑城老板自然会把组装好的电脑送到公司。下面就以这个例子来展开建造者模式的介绍。
## 二、建造者模式的详细介绍
### 2.1 建筑者模式的具体实现
在这个例子中,电脑城的老板是直接与客户(也就是指采购员)联系的,然而电脑的组装是由老板指挥装机人员去把电脑的各个部件组装起来,真真负责创建产品(这里产品指的就是电脑)的人就是电脑城的装机人员。理清了这个逻辑过程之后,下面就具体看下如何用代码来表示这种现实生活中的逻辑过程:
```
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6
7 /// <summary>
8 /// 以组装电脑为例子
9 /// 每台电脑的组成过程都是一致的,但是使用同样的构建过程可以创建不同的表示(即可以组装成不一样的电脑,配置不一样)
10 /// 组装电脑的这个场景就可以应用建造者模式来设计
11 /// </summary>
12 namespace 设计模式之建造者模式
13 {
14 /// <summary>
15 /// 客户类
16 /// </summary>
17 class Customer
18 {
19 static void Main(string[] args)
20 {
21 // 客户找到电脑城老板说要买电脑,这里要装两台电脑
22 // 创建指挥者和构造者
23 Director director = new Director();
24 Builder b1 = new ConcreteBuilder1();
25 Builder b2 = new ConcreteBuilder2();
26
27 // 老板叫员工去组装第一台电脑
28 director.Construct(b1);
29
30 // 组装完,组装人员搬来组装好的电脑
31 Computer computer1 = b1.GetComputer();
32 computer1.Show();
33
34 // 老板叫员工去组装第二台电脑
35 director.Construct(b2);
36 Computer computer2 = b2.GetComputer();
37 computer2.Show();
38
39 Console.Read();
40 }
41 }
42
43 /// <summary>
44 /// 小王和小李难道会自愿地去组装嘛,谁不想休息的,这必须有一个人叫他们去组装才会去的
45 /// 这个人当然就是老板了,也就是建造者模式中的指挥者
46 /// 指挥创建过程类
47 /// </summary>
48 public class Director
49 {
50 // 组装电脑
51 public void Construct(Builder builder)
52 {
53 builder.BuildPartCPU();
54 builder.BuildPartMainBoard();
55 }
56 }
57
58 /// <summary>
59 /// 电脑类
60 /// </summary>
61 public class Computer
62 {
63 // 电脑组件集合
64 private IList<string> parts = new List<string>();
65
66 // 把单个组件添加到电脑组件集合中
67 public void Add(string part)
68 {
69 parts.Add(part);
70 }
71
72 public void Show()
73 {
74 Console.WriteLine("电脑开始在组装.......");
75 foreach (string part in parts)
76 {
77 Console.WriteLine("组件"+part+"已装好");
78 }
79
80 Console.WriteLine("电脑组装好了");
81 }
82 }
83
84 /// <summary>
85 /// 抽象建造者,这个场景下为 "组装人" ,这里也可以定义为接口
86 /// </summary>
87 public abstract class Builder
88 {
89 // 装CPU
90 public abstract void BuildPartCPU();
91 // 装主板
92 public abstract void BuildPartMainBoard();
93
94 // 当然还有装硬盘,电源等组件,这里省略
95
96 // 获得组装好的电脑
97 public abstract Computer GetComputer();
98 }
99
100 /// <summary>
101 /// 具体创建者,具体的某个人为具体创建者,例如:装机小王啊
102 /// </summary>
103 public class ConcreteBuilder1 : Builder
104 {
105 Computer computer = new Computer();
106 public override void BuildPartCPU()
107 {
108 computer.Add("CPU1");
109 }
110
111 public override void BuildPartMainBoard()
112 {
113 computer.Add("Main board1");
114 }
115
116 public override Computer GetComputer()
117 {
118 return computer;
119 }
120 }
121
122 /// <summary>
123 /// 具体创建者,具体的某个人为具体创建者,例如:装机小李啊
124 /// 又装另一台电脑了
125 /// </summary>
126 public class ConcreteBuilder2 : Builder
127 {
128 Computer computer = new Computer();
129 public override void BuildPartCPU()
130 {
131 computer.Add("CPU2");
132 }
133
134 public override void BuildPartMainBoard()
135 {
136 computer.Add("Main board2");
137 }
138
139 public override Computer GetComputer()
140 {
141 return computer;
142 }
143 }
144 }
```
上面代码中都有详细的注释代码,这里就不过多解释,大家可以参考代码和注释来与现实生活中的例子做对比,下图展示了上面代码的运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb3b9af29.png)
### 2.2 建造者模式的定义和类图
介绍完了建造者模式的具体实现之后,下面具体看下建造者模式的具体定义是怎样的。
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式使得建造代码与表示代码的分离,可以使客户端不必知道产品内部组成的细节,从而降低了客户端与具体产品之间的耦合度,下面通过类图来帮助大家更好地理清建造者模式中类之间的关系。
![](https://box.kancloud.cn/2016-01-23_56a2eb3ba851c.png)
## 三、建造者模式的分析
介绍完了建造者模式的具体实现之后,让我们总结下建造模式的实现要点:
1. 在建造者模式中,指挥者是直接与客户端打交道的,指挥者将客户端创建产品的请求划分为对各个部件的建造请求,再将这些请求委派到具体建造者角色,具体建造者角色是完成具体产品的构建工作的,却不为客户所知道。
2. 建造者模式主要用于“分步骤来构建一个复杂的对象”,其中“分步骤”是一个固定的组合过程,而复杂对象的各个部分是经常变化的(也就是说电脑的内部组件是经常变化的,这里指的的变化如硬盘的大小变了,CPU由单核变双核等)。
3. 产品不需要抽象类,由于建造模式的创建出来的最终产品可能差异很大,所以不大可能提炼出一个抽象产品类。
4. 在前面文章中介绍的抽象工厂模式解决了“系列产品”的需求变化,而建造者模式解决的是 “产品部分” 的需要变化。
5. 由于建造者隐藏了具体产品的组装过程,所以要改变一个产品的内部表示,只需要再实现一个具体的建造者就可以了,从而能很好地应对产品组成组件的需求变化。
## 四、.NET 中建造者模式的实现
前面的设计模式在.NET类库中都有相应的实现,那在.NET 类库中,是否也存在建造者模式的实现呢? 然而对于疑问的答案是肯定的,在.NET 类库中,**System.Text.StringBuilder**(存在mscorlib.dll程序集中)就是一个建造者模式的实现。不过它的实现属于建造者模式的演化,此时的建造者模式没有指挥者角色和抽象建造者角色,**StringBuilder**类即扮演着具体建造者的角色,也同时扮演了指挥者和抽象建造者的角色,此时建造模式的实现如下:
```
/// <summary>
/// 建造者模式的演变
/// 省略了指挥者角色和抽象建造者角色
/// 此时具体建造者角色扮演了指挥者和建造者两个角色
/// </summary>
public class Builder
{
// 具体建造者角色的代码
private Product product = new Product();
public void BuildPartA()
{
product.Add("PartA");
}
public void BuildPartB()
{
product.Add("PartB");
}
public Product GetProduct()
{
return product;
}
// 指挥者角色的代码
public void Construct()
{
BuildPartA();
BuildPartB();
}
}
/// <summary>
/// 产品类
/// </summary>
public class Product
{
// 产品组件集合
private IList<string> parts = new List<string>();
// 把单个组件添加到产品组件集合中
public void Add(string part)
{
parts.Add(part);
}
public void Show()
{
Console.WriteLine("产品开始在组装.......");
foreach (string part in parts)
{
Console.WriteLine("组件" + part + "已装好");
}
Console.WriteLine("产品组装完成");
}
}
// 此时客户端也要做相应调整
class Client
{
private static Builder builder;
static void Main(string[] args)
{
builder = new Builder();
builder.Construct();
Product product = builder.GetProduct();
product.Show();
Console.Read();
}
}
```
StringBuilder类扮演着建造string对象的具体建造者角色,其中的ToString()方法用来返回具体产品给客户端(相当于上面代码中GetProduct方法)。其中Append方法用来创建产品的组件(相当于上面代码中BuildPartA和BuildPartB方法),因为string对象中每个组件都是字符,所以也就不需要指挥者的角色的代码(指的是Construct方法,用来调用创建每个组件的方法来完成整个产品的组装),因为string字符串对象中每个组件都是一样的,都是字符,所以Append方法也充当了指挥者Construct方法的作用。
## 五、总结
到这里,建造者模式的介绍就结束了,建造者模式(Builder Pattern),将一个复杂对象的构建与它的表示分离,使的同样的构建过程可以创建不同的表示。建造者模式的本质是使组装过程(用指挥者类进行封装,从而达到解耦的目的)和创建具体产品解耦,使我们不用去关心每个组件是如何组装的。
本专题中所有源码: [建造者模式源码](http://files.cnblogs.com/zhili/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F.rar)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结