深入解析C#设计模式编程中对建造者模式的运用
作者:LearningHard
示例
我们先来以这样一个场景引入:
在电脑城装机总有这样的经历。我们到了店里,先会有一个销售人员来询问你希望装的机器是怎么样的配置,他会给你一些建议,最终会形成一张装机单。和客户确定了装机配置以后,他会把这张单字交给提货的人,由他来准备这些配件,准备完成后交给装机技术人员。技术人员会把这些配件装成一个整机交给客户。
不管是什么电脑,它总是由CPU、内存、主板、硬盘以及显卡等部件构成的,并且装机的过程总是固定的:
- 把主板固定在机箱中
- 把CPU安装到主板上
- 把内存安装到主板上
- 把硬盘连接到主板上
- 把显卡安装到主板上
但是,每台兼容机的部件都各不相同的,有些配置高一点,有些配置低一点,这是变化点。对于装机技术人员来说,他不需要考虑这些配件从哪里来的,他只需要把他们组装在一起了,这是稳定的装机流程。要把这种变化的配件和稳定的流程进行分离就需要引入Builder模式。
示例代码
using System; using System.Collections.Generic; using System.Text; using System.Reflection; namespace BuilderExemple { classProgram { staticvoid Main(string[] args) { ComputerFactory factory = newComputerFactory(); ComputerBuilder office = newOfficeComputerBuilder(); factory.BuildComputer(office); office.Computer.ShowSystemInfo(); ComputerBuilder game = newGameComputerBuilder(); factory.BuildComputer(game); game.Computer.ShowSystemInfo(); } } classComputerFactory { publicvoid BuildComputer(ComputerBuilder cb) { Console.WriteLine(); Console.WriteLine(">>>>>>>>>>>>>>>>>>Start Building " + cb.Name); cb.SetupMainboard(); cb.SetupCpu(); cb.SetupMemory(); cb.SetupHarddisk(); cb.SetupVideocard(); Console.WriteLine(">>>>>>>>>>>>>>>>>>Build " + cb.Name + " Completed"); Console.WriteLine(); } } abstractclassComputerBuilder { protectedstring name; publicstring Name { get { return name; } set { name = value; } } protectedComputer computer; publicComputer Computer { get { return computer; } set { computer = value; } } public ComputerBuilder() { computer = newComputer(); } publicabstractvoid SetupMainboard(); publicabstractvoid SetupCpu(); publicabstractvoid SetupMemory(); publicabstractvoid SetupHarddisk(); publicabstractvoid SetupVideocard(); } classOfficeComputerBuilder : ComputerBuilder { public OfficeComputerBuilder() { name = "OfficeComputer"; } publicoverridevoid SetupMainboard() { computer.Mainboard = "Abit升技LG-95C 主板(Intel 945GC芯片组/LGA 775/1066MHz) "; } publicoverridevoid SetupCpu() { computer.Cpu = "Intel 英特尔赛扬D 336 (2.8GHz/LGA 775/256K/533MHz) "; } publicoverridevoid SetupMemory() { computer.Memory = "Patriot博帝DDR2 667 512MB 台式机内存"; } publicoverridevoid SetupHarddisk() { computer.Harddisk = "Hitachi日立SATAII接口台式机硬盘(80G/7200转/8M)盒装"; } publicoverridevoid SetupVideocard() { computer.Videocard = "主板集成"; } } classGameComputerBuilder : ComputerBuilder { public GameComputerBuilder() { name = "GameComputer"; } publicoverridevoid SetupMainboard() { computer.Mainboard = "GIGABYTE技嘉GA-965P-DS3 3.3 主板(INTEL P965 东莞产)" ; } publicoverridevoid SetupCpu() { computer.Cpu = "Intel 英特尔酷睿E4400 (2.0GHz/LGA 775/2M/800MHz)盒装"; } publicoverridevoid SetupMemory() { computer.Memory = "G.SKILL 芝奇F2-6400CL5D-2GBNQ DDR2 800 1G*2台式机内存"; } publicoverridevoid SetupHarddisk() { computer.Harddisk = "Hitachi日立SATAII接口台式机硬盘(250G/7200转/8M)盒装"; } publicoverridevoid SetupVideocard() { computer.Videocard = "七彩虹逸彩GT-GD3 UP烈焰战神H10 显卡(GeForce 8600GT/256M/DDR3)支持HDMI!"; } } classComputer { privatestring videocard; publicstring Videocard { get { return videocard; } set { videocard = value; } } privatestring cpu; publicstring Cpu { get { return cpu; } set { cpu = value; } } privatestring mainboard; publicstring Mainboard { get { return mainboard; } set { mainboard = value; } } privatestring memory; publicstring Memory { get { return memory; } set { memory = value; } } privatestring harddisk; publicstring Harddisk { get { return harddisk; } set { harddisk = value; } } publicvoid ShowSystemInfo() { Console.WriteLine("==================SystemInfo=================="); Console.WriteLine("CPU:" + cpu); Console.WriteLine("MainBoard:" + mainboard); Console.WriteLine("Memory:" + memory); Console.WriteLine("VideoCard:" + videocard); Console.WriteLine("HardDisk:" + harddisk); } } }
代码说明:
ComputerFactory是建造者模式的指导者。指导者做的是稳定的建造工作,假设它就是一个技术人员,他只是在做按照固定的流程,把配件组装成计算机的重复劳动工作。他不知道他现在组装的是一台游戏电脑还是一台办公用电脑,他也不知道他往主板上安装的内存是1G还是2G的。呵呵,看来是不称职的技术人员。
ComputerBuilder是抽象建造者角色。它主要是用来定义两种接口,一种接口用于规范产品的各个部分的组成。比如,这里就规定了组装一台电脑所需要的5个工序。第二种接口用于返回建造后的产品,在这里我们没有定义抽象方法,反正建造出来的总是电脑。
OfficeComputerBuilder和GameComputerBuilder是具体的建造者。他的工作就是实现各建造步骤的接口,以及实现返回产品的接口,在这里后者省略了。
Computer就是建造出来的复杂产品。在代码中,我们的各种建造步骤都是为创建产品中的各种配件服务的,Computer定义了一个相对具体的产品,在应用中可以把这个产品进行比较高度的抽象,使得不同的具体建造者甚至可以建造出完全不同的产品。
看看客户端的代码,用户先是选择了一个具体的Builder,用户应该很明确它需要游戏电脑还是办公电脑,但是它可以对电脑一无所知,由销售人员给出一个合理的配置单。然后用户让ComputerFactory去为它组装这个电脑。组装完成后ComputerFactory开机,给用户验收电脑的配置是否正确。
你或许觉得ComputerBuilder和是抽象工厂模式中的抽象工厂角色差不多,GameComputerBuilder又像是具体工厂。其实,建造者模式和抽象工厂模式的侧重点不同,前者强调一个组装的概念,一个复杂对象由多个零件组装而成并且组装是按照一定的标准射顺序进行的,而后者强调的是创建一系列产品。建造者模式适用于组装一台电脑,而抽象工厂模式适用于提供用户笔记本电脑、台式电脑和掌上电脑的产品系列。
建造者模式的定义和类图
介绍完了建造者模式的具体实现之后吗,下面具体看下建造者模式的具体定义是怎样的。
建造者模式(Builder Pattern):将一个复杂对象的构建于它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式使得建造代码与表示代码的分离,可以使客户端不必知道产品内部组成的细节,从而降低了客户端与具体产品之间的耦合度,下面通过类图来帮助大家更好地理清建造者模式中类之间的关系。
建造者模式的分析
介绍完了建造者模式的具体实现之后,让我们总结下建造模式的实现要点:
在建造者模式中,指挥者是直接与客户端打交道的,指挥者将客户端创建产品的请求划分为对各个部件的建造请求,再将这些请求委派到具体建造者角色,具体建造者角色是完成具体产品的构建工作的,却不为客户所知道。
建造者模式主要用于“分步骤来构建一个复杂的对象”,其中“分步骤”是一个固定的组合过程,而复杂对象的各个部分是经常变化的(也就是说电脑的内部组件是经常变化的,这里指的的变化如硬盘的大小变了,CPU由单核变双核等)。
产品不需要抽象类,由于建造模式的创建出来的最终产品可能差异很大,所以不大可能提炼出一个抽象产品类。
在前面文章中介绍的抽象工厂模式解决了“系列产品”的需求变化,而建造者模式解决的是 “产品部分” 的需要变化。
由于建造者隐藏了具体产品的组装过程,所以要改变一个产品的内部表示,只需要再实现一个具体的建造者就可以了,从而能很好地应对产品组成组件的需求变化。
.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),将一个复杂对象的构建与它的表示分离,使的同样的构建过程可以创建不同的表示。建造者模式的本质是使组装过程(用指挥者类进行封装,从而达到解耦的目的)和创建具体产品解耦,使我们不用去关心每个组件是如何组装的。