概述:将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
零 场景问题
0.场景描述
- 比如说有这么一个数据导出框架,要求导出文本格式、xml格式、json格式的导出文件。
- 对于这个导出框架,对导导出的内容和格式要求如下:
- 导出文件,无论什么格式,都有三部分组成,文件头、文件体和文件尾。
- 每个文件的导出内容是一样的,但是内容的展现组织形式不同。
- 文件头数据包括:公司编号,导出数据时间。
- 文件体数据包括:表名称,表内容。
- 文件尾数据部分:导出人。
1.不用模式的解决方案
#region 不用设计模式
/// <summary>
/// 描述输出到文件头的内容对象
/// </summary>
public class ExportHeaderModel
{
/// <summary>
/// 部门id
/// </summary>
public string DeptId { get; set; }
/// <summary>
/// 导出时间
/// </summary>
public DateTime ExportTime { get; set; }
}
/// <summary>
/// 描述 输出数据对象
/// </summary>
public class ExportDataModel
{
/// <summary>
/// 产品id
/// </summary>
public string ProductId { get; set; }
/// <summary>
/// 产品价格
/// </summary>
public decimal Price { get; set; }
/// <summary>
/// 销售数量
/// </summary>
public double Amount { get; set; }
}
/// <summary>
/// 描述输出到文件尾的内容的对象
/// </summary>
public class ExportFooterModel
{
/// <summary>
/// 输出人
/// </summary>
public string Exportor { get; set; }
}
/// <summary>
/// 导出数据到txt文件的对象
/// </summary>
public class ExportToTxt
{
public void Export(ExportHeaderModel header, Dictionary<string, IList<ExportDataModel>> exportDatas, ExportFooterModel footer)
{
//1.拼接文件头的内容
var headerContent = $"导出头:导出部门:{header.DeptId};导出时间:{header.ExportTime.ToString("yyyy-MM-dd")}。";
//2.拼接文件体内容
var dataContent = new StringBuilder();
for (int i = 0, ilen = exportDatas.Keys.Count; i < ilen; i++)
{
var tableName = exportDatas.Keys.ElementAt(i);
var data = exportDatas[tableName];
dataContent.Append($"表名:{tableName}{Environment.NewLine}");
for (int j = 0, jlen = data.Count; j < jlen; j++)
{
var product = data[j];
dataContent.Append($"数据第{j + 1}行:产品id:{product.ProductId};价格:{product.Price};销量:{product.Amount}。{Environment.NewLine}");
}
}
//3.拼接文件尾内容
var footerContent = $"导出人:{footer.Exportor}。";
//为了演示的简洁性,这里就不在写出输出文件代码
//以输出到内容到控制台为演示
Console.WriteLine("-------------导出到txt文件-开始-------------");
Console.WriteLine(headerContent);
Console.WriteLine(dataContent.ToString());
Console.WriteLine(footerContent);
Console.WriteLine("-------------导出到txt文件-结束-------------");
}
}
/// <summary>
/// 导出内容到xml文件的对象
/// </summary>
public class ExportToXml
{
public void Export(ExportHeaderModel header, Dictionary<string, IList<ExportDataModel>> exportDatas, ExportFooterModel footer)
{
//1.拼接文件头的内容
var headerContent = $"<header><deptId>{header.DeptId}</deptId><exportTime>{header.ExportTime.ToString("yyyy-MM-dd")}<exportTime></header>";
//2.拼接文件体内容
var dataContent = new StringBuilder();
for (int i = 0, ilen = exportDatas.Keys.Count; i < ilen; i++)
{
var tableName = exportDatas.Keys.ElementAt(i);
var data = exportDatas[tableName];
dataContent.Append($"<tableName>{tableName}{Environment.NewLine}</tableName>");
dataContent.Append(Environment.NewLine);
dataContent.Append($"<dataColumn>");
dataContent.Append(Environment.NewLine);
for (int j = 0, jlen = data.Count; j < jlen; j++)
{
var product = data[j];
dataContent.Append($"<number> { j + 1}</number>");
dataContent.Append(Environment.NewLine);
dataContent.Append($"<productId>{product.ProductId}<productId>");
dataContent.Append(Environment.NewLine);
dataContent.Append($"<price>{product.Price}</price>");
dataContent.Append(Environment.NewLine);
dataContent.Append($"<saleAmount>{product.Amount}</saleAmount>");
dataContent.Append(Environment.NewLine);
}
dataContent.Append($"</dataColumn>");
dataContent.Append(Environment.NewLine);
}
//3.拼接文件尾内容
var footerContent = $"<exportor>{footer.Exportor}</exportor>";
//为了演示的简洁性,这里就不在写出输出文件代码
//以输出到内容到控制台为演示
Console.WriteLine("-------------导出到xml文件-开始-------------");
Console.WriteLine(headerContent);
Console.WriteLine(dataContent.ToString());
Console.WriteLine(footerContent);
Console.WriteLine("-------------导出到xml文件-结束-------------");
}
}
public class BuilderModel_Client
{
public void Run()
{
var headerData = new ExportHeaderModel()
{
DeptId = "地球分公司",
ExportTime = DateTime.Now,
};
var tableData = new Dictionary<string, IList<ExportDataModel>>()
{
{ "第一周表:",
new List<ExportDataModel>()
{
new ExportDataModel()
{
ProductId="水果",
Price=99.8M,
Amount=10,
},
new ExportDataModel()
{
ProductId="家电",
Price=992.8M,
Amount=22,
},
new ExportDataModel()
{
ProductId="服饰",
Price=929.8M,
Amount=59,
},
}
},
};
var footerData = new ExportFooterModel() { Exportor="张三"};
var txtExportor = new ExportToTxt();
txtExportor.Export(headerData,tableData,footerData);
var xmlExprotor = new ExportToXml();
xmlExprotor.Export(headerData, tableData, footerData);
}
}
#endregion
2.分析
- 无论输出到什么格式的文件,步骤基本上是一样的,大致分成了以下四步:
- 拼接文件头内容
- 拼接文件体内容
- 拼接文件尾内容
- 最后把内容输出到文件中
- 也就是说对于不同的输出格式,处理步骤是一样的,但是每个步骤的具体实现是不一样的。
3.存在问题
- 构建每种输出的文件内容的时候,都会重复处理几个步骤,应该提炼出来形成公共的处理过程.
- 今后可能会有很多不同的输出格式的要求,这就需要在处理过程不变的情况下,能方便地切换不同的输出格式。
- 也就是说,构建数据文件的处理过程,应该和具体的步骤实现分开,这样就能够服用处理过程,而且很容易切换不同的输出格式。
一 解决方案
0.生成器模式(Builder)的定义
- 将一个复杂对象的构建与它的表现分离,使得同样的构建过程可以创建不同的表示。
1.用模式来解决问题的思路
- 在列子中,构造数据文件的处理过程就是构建过程;而每种格式的具体的步骤实现就是不同表示。因为不同的步骤具体实现不同,决定了最终的表现也就不同。
- 如何实现同样的构建过程创建不同的表现?
- 把构建过程独立出来,在生成器模式中称为指导者。指导者来指导装配过程,但不负责具体的实现。
- 每步具体的步骤实现,在模式中称为生成器。
- 指导者就是可以重用的构建过程,生成器是可以被切换的步骤具体实现。
2.模式的结构和说明
- Builder:生成器接口,定义创建一个Product对象所需要的各个部件的操作。
- ConcreteBuilder:具体生成器实现,实现各个部件的创建,并负责组装Product对象的各个部件,同时还提供一个让用户获取组装完成后的产品对象的方法。
- Director:指导者,也叫导向者。主要用来使用Builder接口。以一个统一的过程来构建所需要的Product对象。
- Product:产品,表示被生成器构建的复杂对象,包含多个部件。
3.生成器模式表示代码
#region 生成器模式示例代码
/// <summary>
/// 生成器接口
/// 定义创建一个产品对象所需要的各个部件的操作
/// </summary>
public interface IBuilder
{
/// <summary>
/// 示意方法,构建某个部件
/// </summary>
void BuildPart();
}
/// <summary>
/// 被构建产品对象的接口
/// </summary>
public interface IProduct
{
//定义产品的操作
}
/// <summary>
/// 具体的生成器实现对象
/// </summary>
public class ConcreteBuilder:IBuilder
{
/// <summary>
/// 生成器最终构建的产品对象
/// </summary>
private IProduct _resultProduct;
/// <summary>
/// 获取生成器最终构建的产品对象
/// </summary>
/// <returns></returns>
public IProduct GetResult()
=> _resultProduct;
public void BuildPart()
{
//构建摸个部件的功能处理
}
}
/// <summary>
/// 指导者,指导使用生成器的接口来构建产品对象。
/// </summary>
public class Director
{
/// <summary>
/// 持有当前需要使用的生成器对象
/// </summary>
private IBuilder _builder;
/// <summary>
/// 构造方法,传入构造器
/// </summary>
/// <param name="builder"></param>
public Director(IBuilder builder)
{
_builder = builder;
}
/// <summary>
/// 示意方法,指导生成器构建最终的产品对象
/// </summary>
public void Construct()
{
//通过使用生成器接口来构建最终产品对象
_builder.BuildPart();
}
}
#endregion
4.使用生成器模式重写示例
- 重写实例结构图
- 重写实例代码
#region 使用生成器模式重写示例
/// <summary>
/// 生成器接口,定义常见一个输出文件对象所需的各个部件的操作
/// </summary>
public interface IBuilder
{
/// <summary>
/// 构建输出文件的Header部分
/// </summary>
/// <param name="headerModel"></param>
void BuildHeader(ExportHeaderModel headerModel);
/// <summary>
/// 构建输出文件的Body部分
/// </summary>
/// <param name="exportDataModel"></param>
void BuildBody(Dictionary<string, IList<ExportDataModel>> exportDatas);
/// <summary>
/// 构建输出文件的Footer部分
/// </summary>
/// <param name="exportFooterModel"></param>
void BuildFooter(ExportFooterModel exportFooterModel);
}
/// <summary>
/// 生成器对象,实现导出数据到文本文件
/// </summary>
public class TxtBuilder : IBuilder
{
/// <summary>
/// 用来存储文本文件内容,相当于产品
/// </summary>
private StringBuilder _txtFile;
public void BuildHeader(ExportHeaderModel headerModel)
{
//1.拼接文件头的内容
var headerContent = $"导出头:导出部门:{headerModel.DeptId};导出时间:{headerModel.ExportTime.ToString("yyyy-MM-dd")}。";
_txtFile.Append(headerContent);
}
public void BuildBody(Dictionary<string, IList<ExportDataModel>> exportDatas)
{
for (int i = 0, ilen = exportDatas.Keys.Count; i < ilen; i++)
{
var tableName = exportDatas.Keys.ElementAt(i);
var data = exportDatas[tableName];
_txtFile.Append($"表名:{tableName}{Environment.NewLine}");
for (int j = 0, jlen = data.Count; j < jlen; j++)
{
var product = data[j];
_txtFile.Append($"数据第{j + 1}行:产品id:{product.ProductId};价格:{product.Price};销量:{product.Amount}。{Environment.NewLine}");
}
}
}
public void BuildFooter(ExportFooterModel footer)
{
//3.拼接文件尾内容
var footerContent = $"导出人:{footer.Exportor}。";
_txtFile.Append(footerContent);
}
public StringBuilder GetResult()
=> _txtFile;
}
/// <summary>
/// 生成器对策,实现导出数据到xml文件
/// </summary>
public class XmlBuilder : IBuilder
{
/// <summary>
/// 用来存储文本文件内容,相当于产品
/// </summary>
private StringBuilder _txtFile;
public void BuildHeader(ExportHeaderModel header)
{
//1.拼接文件头的内容
var headerContent = $"<header><deptId>{header.DeptId}</deptId><exportTime>{header.ExportTime.ToString("yyyy-MM-dd")}<exportTime></header>";
_txtFile.Append(headerContent);
}
public void BuildBody(Dictionary<string, IList<ExportDataModel>> exportDatas)
{
//2.拼接文件体内容
for (int i = 0, ilen = exportDatas.Keys.Count; i < ilen; i++)
{
var tableName = exportDatas.Keys.ElementAt(i);
var data = exportDatas[tableName];
_txtFile.Append($"<tableName>{tableName}{Environment.NewLine}</tableName>");
_txtFile.Append(Environment.NewLine);
_txtFile.Append($"<dataColumn>");
_txtFile.Append(Environment.NewLine);
for (int j = 0, jlen = data.Count; j < jlen; j++)
{
var product = data[j];
_txtFile.Append($"<number> { j + 1}</number>");
_txtFile.Append(Environment.NewLine);
_txtFile.Append($"<productId>{product.ProductId}<productId>");
_txtFile.Append(Environment.NewLine);
_txtFile.Append($"<price>{product.Price}</price>");
_txtFile.Append(Environment.NewLine);
_txtFile.Append($"<saleAmount>{product.Amount}</saleAmount>");
_txtFile.Append(Environment.NewLine);
}
_txtFile.Append($"</dataColumn>");
_txtFile.Append(Environment.NewLine);
}
}
public void BuildFooter(ExportFooterModel footer)
{
//3.拼接文件尾内容
var footerContent = $"<exportor>{footer.Exportor}</exportor>";
_txtFile.Append(footerContent);
}
public StringBuilder GetResult()
=> _txtFile;
}
/// <summary>
/// 指导者,指导使用生成器的接口来构建输出的文件的对象
/// </summary>
public class Director
{
/// <summary>
/// 只有当前需要使用的生成器对象
/// </summary>
private IBuilder _builder;
/// <summary>
/// 构成方法
/// </summary>
/// <param name="builder">生成器对象</param>
public Director(IBuilder builder)
{
_builder = builder;
}
public void Construct(ExportHeaderModel headerData, Dictionary<string, IList<ExportDataModel>> bodyData, ExportFooterModel footerData)
{
//1.构建header
_builder.BuildHeader(headerData);
//2.构建body
_builder.BuildHeader(headerData);
//3.构建header
_builder.BuildHeader(headerData);
}
public class BuilderModel_Client
{
public void Run()
{
var headerData = new ExportHeaderModel()
{
DeptId = "地球分公司",
ExportTime = DateTime.Now,
};
var tableData = new Dictionary<string, IList<ExportDataModel>>()
{
{ "第一周表:",
new List<ExportDataModel>()
{
new ExportDataModel()
{
ProductId="水果",
Price=99.8M,
Amount=10,
},
new ExportDataModel()
{
ProductId="家电",
Price=992.8M,
Amount=22,
},
new ExportDataModel()
{
ProductId="服饰",
Price=929.8M,
Amount=59,
},
}
},
};
var footerData = new ExportFooterModel() { Exportor = "张三" };
var txtExportor = new ExportToTxt();
txtExportor.Export(headerData, tableData, footerData);
var xmlExprotor = new ExportToXml();
xmlExprotor.Export(headerData, tableData, footerData);
}
}
}
#endregion
二 模式讲解
0.模式的功能
- 主要功能是,构建复杂对象。
- 更为重要的是,这个构建的过程是统一的、固定不变的,变化的部分放到了生成器部分了,只要配置不同的生成器,那么同样的构建过程,就能构建出不同的产品来。
- 直白点说,生成器模式的中心在于分离构建算法和具体的构造实现,从而使得构建算法可以重用。具体的构造实现可以很方便地扩展和切换,从而可以灵活地组合来构建不同的对象。
1.模式的构成
- Builder接口,这里定义了如何构建各个部件,也就是知道每个部件功能如何实现,以及如装配这些部件到产品中去。
- Director,指导者指导如何组合来构建产品,也就是说指导者附则整体的购进算法,而且通常是分步骤来进行的。
- 总结:不管如何变化,Builder模式都存在这么两个部分,一个部分是部件构造和产品装配,另一个部分就是整体构建的算法。
2.模式的本质
- 分离整体构建算法与部件构造。
- 生成器模式的重心还是在于分离整体构架算法和部件构造,而分步骤的构建对象不过是整体构建算法的一个简单实现,或者说是一个附带产物。
3.何时选用
- 如果创建爱你对象的算法,应该独立于该对象的组成部分以及它们的装配方式时。
- 如果同一个构建过程有着不同的表示时。