设计模式之建造者模式(七)
一、引出模式
开发场景:在前面工厂方法模式中我们提到出将数据以XML、TXT和数据库形式导出。现在我们再深化一下,对导出数据的格式进行约束。
导出的文件,不管是格式,都分为文件头、文件体和文件尾
文件头部分,需要描述:分公司名称、数据导出时间,
文件体部分,需要描述:表的名称,单独一行。还有一些数据描述。
文件尾部分,需要描述:导出人。
当我们不使用模式时,不管是输出输出成文本文件还是,输出到XML,步骤基本上是不变的,都要经过以下四步
1) 拼接文件头内容
2) 拼接文件体内容
3) 拼接文件尾内容
4) 将内容输出到相应格式
现在就是存在在使用输出格式时,都会重复这几个处理步骤,我们应该将其提炼出来,形成公共的处理过程,还要保证在处理过程不变的情况下,能方便的切换不同的输出格式。
二、认识模式
1.模式定义
将一个复杂对象的构建与它的表示相分离,使得同样的构建过程可以创建不同的表示。
在上述案例中,四步步骤就是构建过程,不同导出方式就是表示。
2.解决思路
要实现同样的构建过程可以创建不同的表现,第一件事就是将构建过程给独立出来,在建造者模式中把它称为指导者,由它来指导装配的过程,但不负责每步的具体实现。当然,光有指导者是不够的,必须要有能实现每步的对象,在建造者模式中称这些对象为建造器。
这样,指导者就是以重用的构建过程,而建造器是可以被切换的具体实现。
3.模式原型
Builder:建造器接口,定义创建一个Product对象所需要的各个部件的操作。
ConcreteBuilder:具体的建造器实现。实现各个部件的创建,并负责组将Product对象的各个部件,同时还提供一个让用户获取组将完成后的产品对象的方法。
Dorector:指导者,主要用来使用Builder接口,以一个统一程来构建所需要的Product对象。
Product:产品,表示被建造器构建的复杂对象,包含多个部件。
示例代码
class Program { static void Main(string[] args) { Director director = new Director(new ConcreteBuilder()); director.ConStruct(); Console.Read(); } } public class Director { //指导者需要持有建造器的引用 private Builder builders; //指定具体的建造器 public Director(Builder builder) { this.builders = builder; } //间接调用建造器的方法 public void ConStruct() { builders.BuildPart(); } } /// <summary> /// 建造器 /// </summary> public abstract class Builder { public abstract void BuildPart(); } /// <summary> /// 具体的建造器 /// </summary> public class ConcreteBuilder : Builder { //建造器需要持有产品的引用 private Product resultProduct; public override void BuildPart() { Console.WriteLine("BuilderPartA"); } /// <summary> /// 返回产品 /// </summary> /// <returns></returns> public Product GetResult() { return resultProduct; } } /// <summary> /// 产品 /// </summary> public class Product { }
4.上述案例的模式结构
示例代码:
class Program { static void Main(string[] args) { //准备测试数据 ExportHeaderModel ehm = new ExportHeaderModel(); ehm.DepId = "一分公司"; ehm.ExportDate = "2010-05-18"; Dictionary<String, List<ExportDataModel>> dicData = new Dictionary<String, List<ExportDataModel>>(); List<ExportDataModel> list = new List<ExportDataModel>(); ExportDataModel edm1 = new ExportDataModel(); edm1.ProductId = "产品001号"; edm1.Price = 100; edm1.Amount = 80; ExportDataModel edm2 = new ExportDataModel(); edm2.ProductId = "产品002号"; edm2.Price = 99; edm2.Amount = 55; //把数据组装起来 list.Add(edm1); list.Add(edm2); dicData.Add("销售记录表", list); ExportFooterModel efm = new ExportFooterModel(); efm.ExportUser = "张三"; //测试输出到文本文件 TxtBuilder txtBuilder = new TxtBuilder(); //创建指导者对象 Director director = new Director(txtBuilder); director.Construct(ehm, dicData, efm); Console.WriteLine("输出到文本文件的内容:\n" + txtBuilder.GetResult()); Console.WriteLine("--------------------------------------------------------"); //测试输出到xml文件 XmlBuilder xmlBuilder = new XmlBuilder(); Director director2 = new Director(xmlBuilder); director2.Construct(ehm, dicData, efm); //xmlBuilder.BuildHeader(ehm); //xmlBuilder.BuildBody(dicData); //xmlBuilder.BuildFooter(efm); //把要输出的内容输出到控制台看看 Console.WriteLine("输出到XML文件的内容:\n" + xmlBuilder.GetResult()); Console.Read(); } } #region 指导者 /// <summary> /// 指导者,指导使用构建器的接口来构建输出的文件的对象 /// </summary> public class Director { /// <summary> /// 持有当前需要使用的构建器对象 /// </summary> private Builder builder; /// <summary> /// 构造方法,传入构建器对象 /// </summary> /// <param name="builder"></param> public Director(Builder builder) { this.builder = builder; } /// <summary> /// 指导构建器构建最终的输出的文件的对象 /// </summary> /// <param name="ehm">文件头的内容</param> /// <param name="dicData">数据的内容</param> /// <param name="efm">文件尾的内容</param> public void Construct(ExportHeaderModel ehm, Dictionary<String, List<ExportDataModel>> dicData, ExportFooterModel efm) { //可以在这进行校验 //1:先构建Header builder.BuildHeader(ehm); //实现一个Header的后处理 //可以在这里实现一些业务 //2:然后构建Body builder.BuildBody(dicData); //3:然后构建Footer builder.BuildFooter(efm); } } #endregion #region Builder接口 /// <summary> /// 构建器接口,定义创建一个输出文件对象所需的各个部件的操作 /// </summary> public interface Builder { /// <summary> /// 构建输出文件的Header部分 /// </summary> /// <param name="ehm"></param> void BuildHeader(ExportHeaderModel ehm); /// <summary> /// 构建输出文件的Body部分 /// </summary> /// <param name="mapData"></param> void BuildBody(Dictionary<string, List<ExportDataModel>> dicData); /// <summary> /// 构建输出文件的Footer部分 /// </summary> /// <param name="efm"></param> void BuildFooter(ExportFooterModel efm); } #endregion #region ConcreteBuilder 导出到Txt /// <summary> /// 实现导出数据到文本文件的的构建器对象 /// </summary> public class TxtBuilder : Builder { /// <summary> /// 用来记录构建的文件的内容,相当于产品 /// </summary> private StringBuilder strBuilder = new StringBuilder(); public void BuildBody(Dictionary<string, List<ExportDataModel>> dicData) { foreach (var tblName in dicData.Keys) { strBuilder.Append(tblName + "\n"); foreach (ExportDataModel edm in dicData.Values.FirstOrDefault()) { strBuilder.Append(edm.ProductId + "," + edm.Price + "," + edm.Amount); } } } public void BuildFooter(ExportFooterModel efm) { strBuilder.Append(efm.ExportUser); } public void BuildHeader(ExportHeaderModel ehm) { strBuilder.Append(ehm.DepId + "," + ehm.ExportDate + "\n"); } public string GetResult() { return strBuilder.ToString(); } } #endregion #region ConcreteBuilder 导出到XML文件 /// <summary> /// 实现导出数据到XML文件的的构建器对象 /// </summary> public class XmlBuilder : Builder { /// <summary> /// 用来记录构建的文件的内容,相当于产品 /// </summary> private StringBuilder strBuilder = new StringBuilder(); public void BuildHeader(ExportHeaderModel ehm) { strBuilder.Append("<?xml version='1.0' encoding='gb2312'?>\n"); strBuilder.Append("<Report>\n"); strBuilder.Append(" <Header>\n"); strBuilder.Append(" <DepId>" + ehm.DepId + "</DepId>\n"); strBuilder.Append(" <ExportDate>" + ehm.ExportDate + "</ExportDate>\n"); strBuilder.Append(" </Header>\n"); } public void BuildBody(Dictionary<string, List<ExportDataModel>> dicData) { strBuilder.Append(" <Body>\n"); foreach (var tblName in dicData.Keys) { strBuilder.Append(" <Datas TableName=\"" + tblName + "\">\n"); foreach (ExportDataModel edm in dicData.Values.FirstOrDefault()) { strBuilder.Append(" <Data>\n"); strBuilder.Append(" <ProductId>" + edm.ProductId + "</ProductId>\n"); strBuilder.Append(" <Price>" + edm.Price + "</Price>\n"); strBuilder.Append(" <Amount>" + edm.Amount + "</Amount>\n"); strBuilder.Append(" </Data>\n"); } strBuilder.Append(" </Datas>\n"); } strBuilder.Append(" </Body>\n"); } public void BuildFooter(ExportFooterModel efm) { //对象的创建过程 //不是由自己来创建对象,而是使用其它组件创建的对象 //比如:简单工厂、工厂方法 MyFooter mf = FooterFactory.createMyFooter(); //组件组装过程 strBuilder.Append(mf.GenHeader(efm)); } public string GetResult() { return strBuilder.ToString(); } } /// <summary> /// XML 文件尾工厂 /// </summary> public class FooterFactory { public static MyFooter createMyFooter() { return new MyFooter(); } } /// <summary> /// XML 具体实现 /// </summary> public class MyFooter { public String GenHeader(ExportFooterModel efm) { String str = " <Footer>\n"; str += " <ExportUser>" + efm.ExportUser + "</ExportUser>\n"; str += " </Footer>\n"; str += "</Report>\n"; return str; } } #endregion #region 产品 /// <summary> /// 文件头部分 /// </summary> public class ExportHeaderModel { /// <summary> /// 分公司或门市点编号 /// </summary> public String DepId { get; set; } /// <summary> /// 导出数据的日期 /// </summary> public String ExportDate { get; set; } } /// <summary> /// 文件体部分 /// </summary> public class ExportDataModel { /// <summary> /// 产品编号 /// </summary> public string ProductId { get; set; } /// <summary> /// 产品价格 /// </summary> public int Price { get; set; } /// <summary> /// 产品数量 /// </summary> public int Amount { get; set; } } /// <summary> /// 文件尾部分 /// </summary> public class ExportFooterModel { /// <summary> /// 输出人 /// </summary> public String ExportUser { get; set; } } #endregion
三、理解模式
1.认识模式
按照封装变化的原理,建造者模式实则是封装对象创建的变化,主要是指对象内部构建的创建。建造者模式的主要功能就是构建复杂的产品,这个构建的过程是统一的,固定不变的,变化的部分放到建造器部分,只要配置不同的建造器,同样的构建过程,就能出来不同的产品。
建造者模式的重心在于分离构建算法和具体的构造实现。
2.建造者模式的构成
建造者模式分为两个很重要的部分。
1) 一个是Builder接口,这里定义了如何构建各个部件,也就是知道每个部件功能如何实现。
2) 另一部分就是Director,指导者是知道如何组合来构建产品,也就是负责整体的算法。
不论怎么变化,建造者模式都是存在这两个部分的,一部分是部件构造和产品装配,另一部分是整体构建的算法。注意,这里是说部分,而不是一定要存在指导者或者Builder接口这两个类。
3.关于被构建的产品的接口
在使用建造者模式时,大多数情况下是不知道最终构建出来的产品是怎么样的,所以在标准的建造者模式中,一般是不需要给产品定义接口的。
4.建造者模式的演化
建造者模式在使用的过程中可以演化出多种形式。
1)省略抽象建造者角色
如果系统中只需要一个具体建造者的话,可以省略掉抽象建造者。
2)省略指导者角色
在具体建造者只有一个的情况下,如果抽象建造者角色已经被省略掉,那么还可以省略掉指导者角色。让Builder角色自己扮演指导者与建造者双重角色。
只要牢牢把握,建造者模式的两个主要部分(第2点提到)就可以了。
5.建造者模式的优点
1) 松散耦合
2) 可以很容易地改变产品内部表示
3) 更好的复用性
6.何时选用建造者模式
1).如果创建对象的算法,应该独立于该对象的组成不封、 以及它们的装配方式时,使用建造者模式。
2).如果同一个构建过程有着不同的表示时,使用建造者模式。
7.相关模式
建造者模式和工产方法模式
这两个模式可以组合使用。
工厂方法模式与建造者模式并没有泾渭分明的区别,尤其针对建造者模式,可以将其转化为工厂方法模式,反之则不然。也就是说工厂方法模式应用更加广泛。如果一定要做区分,二者都是生产产品的,工厂方法模式主要用于生成一类完整的产品,而建造者模式则更关注对产品内部的创建进行组合,并最终组装为整体的产品。
建造者模式与抽象工厂
这两个模式可以组合使用。
在建造者模式实现中,需要创建各个部件对象,而这些部件对象是有关联的,通常是构成一个复杂对象的部件对象。也就是说,建造者模式实现中,需要获取构成一个复杂对象的产品簇,那么自然就可以使用抽象工厂模式来实现了,这样,抽象工厂负责部件对象的创建,建造者模式实现里则主要负责产品对象的整体构建。
8.小结
建造者模式重心还在于分离整体构建算法和部件构造。分步骤构建对象不过是整体构建算法的一个简单表现,或者说是一个附带的产物。