MiCoos 哟,写bug呢?

设计模式 生成器模式(Builder)


概述:将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

零 场景问题

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.何时选用

  • 如果创建爱你对象的算法,应该独立于该对象的组成部分以及它们的装配方式时。
  • 如果同一个构建过程有着不同的表示时。

上一篇 C#基础 特殊类

Comments

Content