一、基础概念

        生成器模式的本质是【分离整体构建算法和部件构造】;

        生成器模式定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

        生成器模式的功能:主要是用于构建复杂的产品,而且是细化的、分步骤的构建产品【更为重要的是:这个构建过程是统一的,固定不变的;变化的部分都放到生成器部分了,只要配置
不同的生成器,那么同样的构建过程,就能构建出不同的产品来
】(简单地说:生成器模式在于分离构建算法和具体的构造实现;从而使得构建算法可以重用,具体的构造实现可以很方便的扩展和切换,从而可以灵活的组合来构造出不同的产品对象)。

        生成器模式的构成:
                1、生成器接口(Builder),定义了如何构建各个部件(也就知道了每个部件功能如何实现,以及如何装配这些部件到产品中去);
                2、指导者(Director),知道如何组合构建产品(也就是指导者负责整体的构建算法,而且是分步骤执行);

        生成器模式的使用:可以让客户端创建指导者,在指导者里面封装整体构建算法,让指导者去调用生成器接口来封装具体部件的构建功能;

指导者和生成器的交互
序号真正指导者的实现,并不仅仅是简单的按照一定顺序调用生成器的方法来构建对象;
通常是会实现较为复杂的算法或者运算过程,在实际情况下可能会有以下情况
1在运行指导者的时候,会按照整体构建算法的步骤进行运算(可能先运行几步运算,到某一步骤,需要具体创建某个部件对象了,然后在调用生成器接口中创建相应部件的方法来创建具体的部件, 然后在把前面运算的数据传递个生成器接口,因为在生成器内部实现创建和组装部件的时候,可能需要这些数据)                        
2生成器接口创建完具体部件对象后,会把创建好的部件对象返回给指导者,指导者继续后续的算法运算,可能会用到已经创建好的对象
3如此反复下去,知道整个构建算法运行完成,那么最终的产品对象也就创建完成了
生成器模式的优点
序号生成器模式的优点
1

松散耦合

(可用同一个构建算法构建出表现上完全不同的产品,实现产品构建和产品表现上的分离)

2

可以很容易地改变产品的内部表示

(在生成器模式中,由于生成器对象只是提供接口给指导者使用那么具体的部件创建和装配方式是被生成器接口隐藏了,指导者并不知道这些具体的实现细节;这样一来,要想改变产品的内部表示,只需要切换生成器的具体实现即可,不用管指导者,因此变得很容易)

3

更好的复用性

(生成器模式实现构建算法和具体产品实现分离,使得构建产品算法可以复用;同理,具体产品的实现也可以复用,同一个产品的实现,可以配合不同的构建算法使用) 

        何时选用生成器模式?

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

二、生成器模式示例

        业务需求:前面我们在使用工厂方法模式中提到导出数据的应用框架中,对于导出的数据,会有一些约定的方式,如导出为:文本格式、数据库格式、xml格式等;但是对于导出具体的格式并没有做实现;现在我们需要实现导出具体的格式内容有如下要求:

        1、导出的文件,不管什么格式,都有三个内容组成【文件头】、【文件体】、【文件尾】;

        2、对于文件头:需要描述:公司或门市编号、导出数据日期(其中文本文件格式对包含内容使用逗号分隔);

        3、对于文件体:需要描述:该表名称,然后每行表示一条数据(其中文本文件中每条数据内容使用逗号分隔);

        4、对于文件尾:需要描述:输出人;

        我们这里为了演示简单明了,就只对如何实现导出文件进行演示示例,前面工厂方法模式实现的内容就不做重复了。这里仅以导出具体的文本文件、XML文件为例示意,重在理解生成器模式:

 2.1、不使用模式的示例

        1、根据需求说明,无论导出何种格式文件,都包含文件头、文件体、文件尾三个内容,我们就先创建者三个内容的实体对象:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    /// <summary>
    /// 描述输出到文件头的内容对象
    /// </summary>
    internal class ExportHeaderModel
    {
        //分公司或门市店编号
        private string? depId;
        //导出数据的日期
        private string? exportDate;

        public string DepId { get => depId; set => depId = value; }
        public string ExportDate { get => exportDate; set => exportDate = value; }

    }//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    /// <summary>
    /// 描述输出数据的对象
    /// </summary>
    internal class ExportDataModel
    {
        //产品编号
        private string? productId;
        //销售价格
        private double price;
        //销售数量
        private double amount;

        public string ProductId { get => productId; set => productId = value; }
        public double Price { get => price; set => price = value; }
        public double Amount { get => amount; set => amount = value; }

    }//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    /// <summary>
    /// 描述输出到文件尾的内容对象
    /// </summary>
    internal class ExportFooterModel
    {
        //输出人
        private string? exportUser;

        public string ExportUser { get => exportUser; set => exportUser = value; }

    }//Class_end
}

2、由于这里涉及到保存文件,先编写一个简单保存文件的帮助类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    /// <summary>
    /// 保存文件
    /// </summary>
    internal class SaveFile
    {
        private static string path=AppDomain.CurrentDomain.BaseDirectory+"Builder";

        //文件输出路径
        public static string Path 
        {
            get 
            {
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                return path;       
            }
        
        }

        /// <summary>
        /// 保存文件
        /// </summary>
        /// <param name="fileName">文件名称</param>
        /// <param name="fileInfo">文件内容信息</param>
        /// <returns></returns>
        public static bool Save(string fileName,string fileInfo)
        {
            bool result = false;
            if (string.IsNullOrEmpty(fileName)||string.IsNullOrEmpty(fileInfo)) 
            { 
                return result;
            }

            string filePathAndName = $"{Path}\\{fileName}";
            File.WriteAllText(filePathAndName, fileInfo,Encoding.UTF8);
            result = true;
            return result;
        }

    }//Class_end
}

3、编写导出为文本文件的类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    /// <summary>
    /// 导出数据到文本文件
    /// </summary>
    internal class ExportToTxt
    {
        /// <summary>
        /// 导出数据
        /// </summary>
        /// <param name="ehm">文件头内容</param>
        /// <param name="dicDatas">数据内容</param>
        /// <param name="efm">文件尾内容</param>
        public void Export(ExportHeaderModel ehm,Dictionary<string,List<ExportDataModel>> dicDatas,
            ExportFooterModel efm)
        {
            StringBuilder sb=new StringBuilder();

            //1、先来拼接文件头的内容
            sb.Append($"{ehm.DepId},{ehm.ExportDate}\n");

            //2、拼接文件体内容
            foreach (var tableName in dicDatas.Keys)
            {
                //先拼接表名称
                sb.Append($"{tableName}\n");
                //然后在循环拼接具体数据
                if (dicDatas.ContainsKey(tableName))
                {
                    foreach (ExportDataModel item in dicDatas[tableName])
                    {
                        sb.AppendJoin(',',item.ProductId,item.Price,item.Amount);
                        sb.Append("\n");
                    }
                }
            }

            //3、构建文件尾部内容
            sb.Append(efm.ExportUser);

            //4、保存文件
            string fileName = "文本内容.txt";
            bool result = SaveFile.Save(fileName,sb.ToString());

            Console.WriteLine($"保存内容到【{fileName}】结果是:{result},具体内容如下:");
            Console.WriteLine($"{sb}\n\n");
            
        }

    }//Class_end
}

 4、编写导出为XML文件的类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern
{
    /// <summary>
    /// 导出数据到XML文件
    /// </summary>
    internal class ExportToXml
    {
        /// <summary>
        /// 导出数据
        /// </summary>
        /// <param name="ehm">文件头内容</param>
        /// <param name="dicDatas">数据内容</param>
        /// <param name="efm">文件尾内容</param>
        public void Export(ExportHeaderModel ehm, Dictionary<string, List<ExportDataModel>> dicDatas,
            ExportFooterModel efm)
        {
            StringBuilder sb = new StringBuilder();

            //1、先拼接文件头内容
            sb.Append($"<?xml version='1.0' encoding='utf-8'?>\n");
            sb.Append("<Report>\n");
            sb.Append("  <Header>\n");
            sb.Append($"    <DepId>{ehm.DepId}</DepId>\n");
            sb.Append($"    <ExportDate>{ehm.ExportDate}</ExportDate>\n");
            sb.Append("  </Header>\n");

            //2、拼接文件内容
            sb.Append("  <body>\n");
            foreach (var tableName in dicDatas.Keys)
            {
                //先拼接表名称
                sb.Append($"    <Datas TableName=\"{tableName}\">\n");
                //循环拼接具体数据
                foreach (ExportDataModel item in dicDatas[tableName])
                {
                    sb.Append("      <Data>\n");
                    sb.Append($"        <ProductId>{item.ProductId}</ProductId>\n");
                    sb.Append($"        <Price>{item.Price}</Price>\n");
                    sb.Append($"        <Amount>{item.Amount}</Amount>\n");
                    sb.Append("      </Data>\n");
                }
                sb.Append($"    </Datas>\n");
            }
            sb.Append("  </body>\n");

            //3、拼接文件尾内容
            sb.Append("  <Footer>\n");
            sb.Append($"    <ExportUser>{efm.ExportUser}</ExportUser>\n");
            sb.Append("  </Footer>\n");
            sb.Append("</Report>\n");

            //4、保存文件
            string fileName = "XML内容.xml";
            bool result = SaveFile.Save(fileName,sb.ToString());

            Console.WriteLine($"保存内容到【{fileName}】结果是:{result},具体内容如下:");
            Console.WriteLine($"{sb}\n\n");
        }


    }//Class_end
}

5、客户端的使用方法

using BuilderPattern.Builder;
using BuilderPattern.BuilderComplexObj;

namespace BuilderPattern
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ExportFormatterTest();

            Console.ReadLine();
        }

        /// <summary>
        /// 导出格式测试
        /// </summary>
        private static void ExportFormatterTest()
        {
            Console.WriteLine($"------导出格式测试------\n");

            ExportHeaderModel ehm=new ExportHeaderModel();
            ehm.DepId = "XXX一分公司";
            ehm.ExportDate=DateTime.Now.ToLongDateString();

            Dictionary<string, List<ExportDataModel>> dicDatas = new Dictionary<string, List<ExportDataModel>>();
            List<ExportDataModel> listDatas = new List<ExportDataModel>();

            ExportDataModel edm1 =new ExportDataModel();
            edm1.ProductId = "产品01";
            edm1.Price = 100;
            edm1.Amount = 101;

            ExportDataModel edm2 = new ExportDataModel();
            edm2.ProductId = "产品02";
            edm2.Price = 200;
            edm2.Amount = 202;

            listDatas.Add(edm1);
            listDatas.Add(edm2);
            dicDatas.Add("XXX记录表",listDatas);

            ExportFooterModel efm =new ExportFooterModel();
            efm.ExportUser = "Coffee";

            //测试导出为文本文件
            ExportToTxt toTxt =new ExportToTxt();
            toTxt.Export(ehm,dicDatas,efm);

            //导出为XML文件
            ExportToXml toXml =new ExportToXml();
            toXml.Export(ehm,dicDatas,efm);
        }

    }//Class_end
}

6、运行结果如下:

        上面的方法已经实现了我们想要的功能,但是我们在实现的时候发现,不论导出那种格式文件的步骤都是一样,都是按照:

 1、拼接文件头内容;

 2、拼接文件体内容;

 3、拼接文件尾内容;

 4、保存文件;

        但是每个步骤具体的实现是不同的;我们是否可以将相同的处理步骤提取出来复用,对于每个步骤不同的实现单独处理呢?【如下的生成器模式就可以解决整个问题】。

 2.2、使用典型生成器模式示例

1、创建生成器接口,将涉及到的步骤行为都定义出来

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern.Builder
{
    /// <summary>
    /// 导出流程接口,定义构建一个输出文件对象所需的各个部件操作
    /// </summary>
    internal interface IExportBuilder
    {
        /// <summary>
        /// 构建输出文件的Header部分
        /// </summary>
        /// <param name="ehm">文件头对象</param>
        void BuildHeader(ExportHeaderModel ehm);

        /// <summary>
        /// 构建输出文件的Body部分
        /// </summary>
        /// <param name="dicDatas">文件内容主体</param>
        void BuildBody(Dictionary<string,List<ExportDataModel>> dicDatas);

        /// <summary>
        /// 构建输出文件的Footer部分
        /// </summary>
        /// <param name="ehm">文件尾部对象</param>
        void BuildFooter(ExportFooterModel efm);

        /// <summary>
        /// 需保存的文件内容
        /// </summary>
        string NeedSaveFileContent { get; }

    }//Interface_end
}

2、创建文本文件类继承生成器接口,实现具体的步骤内容

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace BuilderPattern.Builder
{
    /// <summary>
    /// 实现导出数据到文件文件的生成器对象
    /// </summary>
    internal class TxtBuilder : IExportBuilder
    {
        //定义用来组装数据的对象
        private StringBuilder sb = new StringBuilder();


        public void BuildHeader(ExportHeaderModel ehm)
        {
            sb.Append($"{ehm.DepId},{ehm.ExportDate}\n");
        }

        public void BuildBody(Dictionary<string, List<ExportDataModel>> dicDatas)
        {
            if (dicDatas == null || dicDatas.Count <= 0) return;

            foreach (var tableName in dicDatas.Keys)
            {
                //先拼接表名称
                sb.Append($"{tableName}\n");
                //然后在循环拼接具体数据
                if (dicDatas.ContainsKey(tableName))
                {
                    foreach (ExportDataModel item in dicDatas[tableName])
                    {
                        sb.AppendJoin(',', item.ProductId, item.Price, item.Amount);
                        sb.Append("\n");
                    }
                }
            }
        }

        public void BuildFooter(ExportFooterModel efm)
        {
            sb.Append(efm.ExportUser);
        }

        public string NeedSaveFileContent =>sb.ToString();

    }//Class_end
}

3、创建XML文件类继承生成器接口,实现具体的步骤内容

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace BuilderPattern.Builder
{
    /// <summary>
    /// 实现导出数据到XML的生成器对象
    /// </summary>
    internal class XmlBuilder : IExportBuilder
    {
        //定义用来组装数据的对象
        private StringBuilder sb = new StringBuilder();

        public void BuildHeader(ExportHeaderModel ehm)
        {
            sb.Append($"<?xml version='1.0' encoding='utf-8'?>\n");
            sb.Append("<Report>\n");
            sb.Append("  <Header>\n");
            sb.Append($"    <DepId>{ehm.DepId}</DepId>\n");
            sb.Append($"    <ExportDate>{ehm.ExportDate}</ExportDate>\n");
            sb.Append("  </Header>\n");
        }
        public void BuildBody(Dictionary<string, List<ExportDataModel>> dicDatas)
        {
            sb.Append("  <body>\n");
            foreach (var tableName in dicDatas.Keys)
            {
                //先拼接表名称
                sb.Append($"    <Datas TableName=\"{tableName}\">\n");
                //循环拼接具体数据
                foreach (ExportDataModel item in dicDatas[tableName])
                {
                    sb.Append("      <Data>\n");
                    sb.Append($"        <ProductId>{item.ProductId}</ProductId>\n");
                    sb.Append($"        <Price>{item.Price}</Price>\n");
                    sb.Append($"        <Amount>{item.Amount}</Amount>\n");
                    sb.Append("      </Data>\n");
                }
                sb.Append($"    </Datas>\n");
            }
            sb.Append("  </body>\n");
        }

        public void BuildFooter(ExportFooterModel efm)
        {
            sb.Append("  <Footer>\n");
            sb.Append($"    <ExportUser>{efm.ExportUser}</ExportUser>\n");
            sb.Append("  </Footer>\n");
            sb.Append("</Report>\n");
        }

        public string NeedSaveFileContent => sb.ToString();

    }//Class_end
}

4、创建一个指导者类,用来指导具体的产品组装

/***
*	Title:"设计模式" 项目
*		主题:生成器模式
*	Description:
*	    基础概念:生成器模式的本质是【分离整体构建算法和部件构造】
*	              
*	    生成器模式定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示          
*	        
*	    
*	    生成器模式的构成:
*	                    1、生成器接口(Builder),定义了如何构建各个部件(也就知道了每个部件功能如何实现,以及如何装配这些部件到产品中去)
*		                2、指导者(Director),知道如何组合构建产品(也就是指导者负责整体的构建算法,而且是分步骤执行)
*		
*		生成器模式的使用:可以让客户端创建指导者,在指导者里面封装整体构建算法,让指导者去调用生成器接口来封装具体部件的构建功能
*		                
*		
*		 关于被构建的产品接口:在使用生成器模式的时候,大多数情况下不知道最终构建出来的产品是什么样的,
*		                       所以在标准的生成器模式里面,一般是不需要对产品定义抽象接口的(因为最终构造的产品千差万别,
*		                       刚给这些产品定义公共接口几乎没有意义)		    
*		 
*		 何时选用生成器模式:
*		                1、如果创建对象的算法,应该独立于该对象的组成部分以及它们的装配方式时;
*		                2、如果同一个构建过程有着不同的表示时
*		 
*	Date:2025
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:
 ***/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern.Builder
{
    /// <summary>
    /// 指导者,指导使用生成器的接口来构建输出的文件对象
    /// </summary>
    internal class ExportDirector
    {
        //持有当前需使用的生成器对象
        private IExportBuilder builder;

        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="exportBuilder">导出的生成器对象</param>
        public ExportDirector(IExportBuilder exportBuilder)
        {
            this.builder = exportBuilder;
        }

        /// <summary>
        /// 指导生成器构建流程并输出
        /// </summary>
        /// <param name="ehm">文件头对象</param>
        /// <param name="dicDatas">文件内容</param>
        /// <param name="efm">文件尾对象</param>
        /// <param name="fileName">导出的文件名称</param>
        public void BuildExportFlow(ExportHeaderModel ehm,Dictionary<string,List<ExportDataModel>> dicDatas,
            ExportFooterModel efm,string fileName)
        {
            //1、先构建Header
            builder.BuildHeader(ehm);

            //2、构建Body
            builder.BuildBody(dicDatas);

            //3、构建Footer
            builder.BuildFooter(efm);

            //4、保存文件
            bool result = SaveFile.Save(fileName,builder.NeedSaveFileContent);

            Console.WriteLine($"保存内容到【{fileName}】结果是:{result},具体内容如下:");
            Console.WriteLine($"{builder.NeedSaveFileContent}\n\n");
        }


    }//Class_end
}

5、客户端实现

using BuilderPattern.Builder;
using BuilderPattern.BuilderComplexObj;

namespace BuilderPattern
{
    internal class Program
    {
        static void Main(string[] args)
        {

            ExportBuilderTest();

            Console.ReadLine();
        }
      
        /// <summary>
        /// 导出生成器测试
        /// </summary>
        private static void ExportBuilderTest()
        {
            Console.WriteLine($"------导出生成器测试------\n");

            ExportHeaderModel ehm = new ExportHeaderModel();
            ehm.DepId = "XXX一分公司";
            ehm.ExportDate = DateTime.Now.ToLongDateString();

            Dictionary<string, List<ExportDataModel>> dicDatas = new Dictionary<string, List<ExportDataModel>>();
            List<ExportDataModel> listDatas = new List<ExportDataModel>();

            ExportDataModel edm1 = new ExportDataModel();
            edm1.ProductId = "产品01";
            edm1.Price = 100;
            edm1.Amount = 101;

            ExportDataModel edm2 = new ExportDataModel();
            edm2.ProductId = "产品02";
            edm2.Price = 200;
            edm2.Amount = 202;

            listDatas.Add(edm1);
            listDatas.Add(edm2);
            dicDatas.Add("XXX记录表", listDatas);

            ExportFooterModel efm = new ExportFooterModel();
            efm.ExportUser = "Coffee";

            //测试导出为文本文件
            ExportDirector exportDirector1 = new ExportDirector(new TxtBuilder());
            exportDirector1.BuildExportFlow(ehm,dicDatas,efm,"指导者构建导出流程保存TXT文件.txt");


            //导出为XML文件
            ExportDirector exportDirector2 = new ExportDirector(new XmlBuilder());
            exportDirector2.BuildExportFlow(ehm, dicDatas, efm, "指导者构建导出流程保存XML文件.xml");
        }

    }//Class_end
}

6、运行结果如下:

 2.3、使用生成器模式构建复杂对象

        考虑这样的一个应用:我们需要创建一个保险合同,里面的很多属性值都有约束;要求创建出来的对象是满足这些约束规则的(约束的规则如:合同只能与个人或者公司签订,不能同时与个人或公司签订;合同的签订生效日期必须小于失效日期等);

        对于创建复杂的对象,可能会有多种不同的选择与步骤,可以直接去掉【指导者】,把指导者的功能和客户端合并起来(即:客户端相当于指导者,它来指导构建器去构建需要的复杂对象):

  2.3.1、使用Builder先创建不带约束的复杂对象

1、先创建一个保险合同对象

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern.BuilderComplexObj
{
    /// <summary>
    /// 保险合同对象
    /// </summary>
    internal class InsuranceContract
    {
        //保险合同编号
        private string contractId;

        /*被保险人员的名称,同一份保险合同,要么与人员签订,要么与公司签订
         * 也就是说:【被保险人员】和【被保险公司】这两个属性,不能同时有值
         */
        private string personName;

        //被保险公司名称
        private string companyName;

        //保险开始生效日期
        private DateTime beginDate;

        //保险失效日期,且一定要大于保险开始生效日期
        private DateTime endDate;

        //其他数据
        private string otherData;

        private InsuranceContract()
        {
            
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="builder">合同构建器</param>
        public InsuranceContract(InsuranceContractBuilder builder)
        {
            this.contractId = builder.ContractId;
            this.personName = builder.PersonName;
            this.companyName = builder.CompanyName;
            this.beginDate = builder.BeginDate;
            this.endDate = builder.EndDate;
            this.otherData = builder.OtherData;
        }

        /// <summary>
        /// 打印合同信息
        /// </summary>
        public void PrintInfo()
        {
            Console.WriteLine($"合同编号:{contractId} 被保人员名称:{personName} 被保公司名称:{companyName} " +
                $"合同生效日期:{beginDate} 合同失效日期:{endDate} 其他数据:{otherData}");
        }

    }//Class_end
}

2、创建合同生成器对象

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern.BuilderComplexObj
{
    /// <summary>
    /// 保险合同构建器
    /// </summary>
    internal class InsuranceContractBuilder
    {
       public string? ContractId { get; set; }
       public string? PersonName { get; set; }
       public string? CompanyName { get; set; }
       public DateTime BeginDate { get; set; }
       public DateTime EndDate { get; set; }
       public string? OtherData { get; set; }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="contractId">合同编号</param>
        /// <param name="beginDate">开始日期</param>
        /// <param name="endDate">结束日期</param>
        public InsuranceContractBuilder(string contractId,DateTime beginDate,DateTime endDate)
        {
            this.ContractId = contractId;
            this.BeginDate = beginDate;
            this.EndDate = endDate;     
        }

        /// <summary>
        /// 选填数据,被保险人人员名称
        /// </summary>
        /// <param name="personName"></param>
        /// <returns></returns>
        public InsuranceContractBuilder WithPersonName(string personName)
        {
            this.PersonName = personName;
            return this;
        }

        /// <summary>
        /// 选填数据,被保险公司的名称
        /// </summary>
        /// <param name="companyName">被保险公司名称</param>
        /// <returns></returns>
        public InsuranceContractBuilder WithCompanyName(string companyName)
        {
            this.CompanyName = companyName;
            return this;
        }

        /// <summary>
        /// 选填数据,其他数据
        /// </summary>
        /// <param name="otherData">其他数据</param>
        /// <returns></returns>
        public InsuranceContractBuilder WithOtherData(string otherData)
        {
            this.OtherData = otherData;
            return this;
        }

        //构建真正的对象并返回
        public InsuranceContract Build()
        {
            return new InsuranceContract(this);
        }


    }//Class_end
}

3、客户端使用保险合同

using BuilderPattern.Builder;
using BuilderPattern.BuilderComplexObj;

namespace BuilderPattern
{
    internal class Program
    {
        static void Main(string[] args)
        {
            InsuranceContractBuilderTest();

            Console.ReadLine();
        }

        //保险合同构建器对象测试
        private static void InsuranceContractBuilderTest()
        {
            //创建构建器对象
            InsuranceContractBuilder builder = new InsuranceContractBuilder("CK250429001",
               Convert.ToDateTime(DateTime.Now.ToLongDateString()), Convert.ToDateTime(DateTime.Now.ToLongDateString()));

            //设置需要的数据,然后构建合同对象
            InsuranceContract insuranceContract = builder
                .WithPersonName("张三")
                .WithCompanyName("XXX科技有限公司")
                .WithOtherData("测试内容")
                .Build();

            //打印合同信息
            insuranceContract.PrintInfo();
        }

    }//Class_end
}

4、运行结果如下:

  2.3.2、使用Builder创建带约束的复杂对象

         通常可以在两个地方添加约束规则:

        《1》在构建器的每个属性字段的Set方法里面进行单个数据的约束校验,若不正确则抛出异常;

        《2》在构建器的Build方法里面创建,即在创建保险合同对象之前,对所有的数据都可以进行数据约束校验,尤其是那些涉及到几个数据之间关联的约束,在这里校验会很合适,若校验不通过则抛出异常;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern.BuilderComplexObj
{
    /// <summary>
    /// 保险合同构建器
    /// </summary>
    internal class InsuranceContractBuilder
    {
       public string? ContractId { get; set; }
       public string? PersonName { get; set; }
       public string? CompanyName { get; set; }
       public DateTime BeginDate { get; set; }
       public DateTime EndDate { get; set; }
       public string? OtherData { get; set; }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="contractId">合同编号</param>
        /// <param name="beginDate">开始日期</param>
        /// <param name="endDate">结束日期</param>
        public InsuranceContractBuilder(string contractId,DateTime beginDate,DateTime endDate)
        {
            this.ContractId = contractId;
            this.BeginDate = beginDate;
            this.EndDate = endDate;     
        }

        /// <summary>
        /// 选填数据,被保险人人员名称
        /// </summary>
        /// <param name="personName"></param>
        /// <returns></returns>
        public InsuranceContractBuilder WithPersonName(string personName)
        {
            this.PersonName = personName;
            return this;
        }

        /// <summary>
        /// 选填数据,被保险公司的名称
        /// </summary>
        /// <param name="companyName">被保险公司名称</param>
        /// <returns></returns>
        public InsuranceContractBuilder WithCompanyName(string companyName)
        {
            this.CompanyName = companyName;
            return this;
        }

        /// <summary>
        /// 选填数据,其他数据
        /// </summary>
        /// <param name="otherData">其他数据</param>
        /// <returns></returns>
        public InsuranceContractBuilder WithOtherData(string otherData)
        {
            this.OtherData = otherData;
            return this;
        }

        //构建真正的对象并返回
        public InsuranceContract Build()
        {
            /*在构建这里添加合同内容的校验逻辑*/
            if (string.IsNullOrEmpty(ContractId))
            {
                throw new Exception($"合同编号不能为空");
            }

            if (string.IsNullOrEmpty(PersonName) && string.IsNullOrEmpty(CompanyName))
            {
                throw new Exception("一份保险合同不能没有签订对象");
            }
            if (!string.IsNullOrEmpty(PersonName)&&!string.IsNullOrEmpty(CompanyName))
            {
                throw new Exception("【被保险人】与【被保险公司】只能有一个");
            }

            if (BeginDate<DateTime.Now.Date || EndDate<DateTime.Now.Date)
            {
                throw new Exception("保险生效日期与失效日期不能小于当前日期");
            }

            if (BeginDate>=EndDate)
            {
                throw new Exception("合同的保险生效日期必须小于失效日期,请检查后重试!!!");
            }
            

            return new InsuranceContract(this);
        }


    }//Class_end
}

客户端调用如下:

using BuilderPattern.Builder;
using BuilderPattern.BuilderComplexObj;

namespace BuilderPattern
{
    internal class Program
    {
        static void Main(string[] args)
        {
            InsuranceContractBuilderTest2();

            Console.ReadLine();
        }

        //保险合同构建器对象测试
        private static void InsuranceContractBuilderTest2()
        {
            //创建构建器对象
            InsuranceContractBuilder builder = new InsuranceContractBuilder("CK250429001",
               Convert.ToDateTime(DateTime.Now.ToLongDateString()), Convert.ToDateTime(DateTime.Now.AddDays(1).ToLongDateString()));

            //设置需要的数据,然后构建合同对象
            InsuranceContract insuranceContract = builder
                .WithPersonName("张三")
                //.WithCompanyName("XXX科技有限公司")
                .WithOtherData("测试内容")
                .Build();

            //打印合同信息
            insuranceContract.PrintInfo();
        }

    }//Class_end
}

运行结果如下:

  2.3.3、把构建器和被构建对象合并

        在实际开发中,如果构建器对象和被构建的对象是分开的话,可能会导致同包内的对象不使用构建器来构建对象, 这会导致错误;并且构建器就是为了创建被构建的对象,完全可以不用单独创建一个类【我们可以直接在被构建对象内使用内联类,这样一体化更加简单与直观,不容易出错,也更容易理解】。由于使用生成器模式来创建某个对象,因此就没有必要在定义一个生成器接口,直接提供一个具体的构建器类就可以了;对于创建一个复杂的对象,可能会有很多种选择和步骤,干脆就去掉指导者,把指导者的功能和客户端功能结合起来(即:客户端就相当于指导者,用来指导构建器类去构建需要的复杂对象)。

/***
*	Title:"设计模式" 项目
*		主题:生成器模式
*	Description:
*	    功能:将构建器与被构建对象合并在一起
*	    说明:在实际开发中,如果构建器对象和被构建的对象是分开的话,可能会导致同包内的对象不使用构建器来构建对象,
*	          这会导致错误;并且构建器就是为了创建被构建的对象,完全可以不用单独创建一个类【我们可以直接在被构建对象
*	          内使用内联类,这样一体化更加简单与直观,不容易出错,也更容易理解】
*	          
*	Date:2025
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:
 ***/

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BuilderPattern.BuilderComplexObj
{
    internal class InsuranceContract2
    {
        //保险合同编号
        private string? contractId;
        /*被保险人员的名称,同一份保险合同,要么与人员签订,要么与公司签订
        * 也就是说:【被保险人员】和【被保险公司】这两个属性,不能同时有值
        */
        private string? personName;

        //被保险公司名称
        private string? companyName;

        //保险开始生效日期
        private DateTime beginDate;

        //保险失效日期,且一定要大于保险开始生效日期
        private DateTime endDate;

        //其他数据
        private string? otherData;

        private InsuranceContract2()
        {
                
        }

        /// <summary>
        /// 私有构造方法
        /// </summary>
        /// <param name="builder"></param>
        private InsuranceContract2(Builder builder)
        {
            this.contractId = builder.ContractId;
            this.personName = builder.PersonName;
            this.companyName = builder.CompanyName;
            this.beginDate = builder.BeginDate;
            this.endDate = builder.EndDate;
            this.otherData = builder.OtherData;
        }


        /// <summary>
        /// 打印合同信息
        /// </summary>
        public void PrintInfo()
        {
            Console.WriteLine($"合同编号:{contractId} 被保人员名称:{personName} 被保公司名称:{companyName}" +
                $"合同生效日期:{beginDate} 合同失效日期:{endDate} 其他数据:{otherData}");
        }



        //保险合同构建器(用作保险合同的类级内部类)
        public class Builder
        {
            public string? ContractId { get; set; }
            public string? PersonName { get; set; }
            public string? CompanyName { get; set; }
            public DateTime BeginDate { get; set; }
            public DateTime EndDate { get; set; }
            public string? OtherData { get; set; }

            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="contractId">合同编号</param>
            /// <param name="beginDate">开始日期</param>
            /// <param name="endDate">结束日期</param>
            public Builder(string contractId, DateTime beginDate, DateTime endDate)
            {
                this.ContractId = contractId;
                this.BeginDate = beginDate;
                this.EndDate = endDate;
            }

            /// <summary>
            /// 选填数据,被保险人人员名称
            /// </summary>
            /// <param name="personName"></param>
            /// <returns></returns>
            public Builder WithPersonName(string personName)
            {
                this.PersonName = personName;
                return this;
            }

            /// <summary>
            /// 选填数据,被保险公司的名称
            /// </summary>
            /// <param name="companyName">被保险公司名称</param>
            /// <returns></returns>
            public Builder WithCompanyName(string companyName)
            {
                this.CompanyName = companyName;
                return this;
            }

            /// <summary>
            /// 选填数据,其他数据
            /// </summary>
            /// <param name="otherData">其他数据</param>
            /// <returns></returns>
            public Builder WithOtherData(string otherData)
            {
                this.OtherData = otherData;
                return this;
            }

            //构建真正的对象并返回
            public InsuranceContract2 Build()
            {
                /*在构建这里添加合同内容的校验逻辑*/
                if (string.IsNullOrEmpty(ContractId))
                {
                    throw new Exception($"合同编号不能为空");
                }

                if (string.IsNullOrEmpty(PersonName) && string.IsNullOrEmpty(CompanyName))
                {
                    throw new Exception("一份保险合同不能没有签订对象");
                }
                if (!string.IsNullOrEmpty(PersonName) && !string.IsNullOrEmpty(CompanyName))
                {
                    throw new Exception("【被保险人】与【被保险公司】只能有一个");
                }

                if (BeginDate < DateTime.Now.Date || EndDate < DateTime.Now.Date)
                {
                    throw new Exception("保险生效日期与失效日期不能小于当前日期");
                }

                if (BeginDate >= EndDate)
                {
                    throw new Exception("合同的保险生效日期必须小于失效日期,请检查后重试!!!");
                }


                return new InsuranceContract2(this);
            }
        }


    }//Class_end
}

客户端调用构建器与保险合同对象合并内容

using BuilderPattern.Builder;
using BuilderPattern.BuilderComplexObj;

namespace BuilderPattern
{
    internal class Program
    {
        static void Main(string[] args)
        {

            InsuranceContractBuilderTest3();

            Console.ReadLine();
        }

        //保险合同构建器对象测试
        private static void InsuranceContractBuilderTest3()
        {

            //创建构建器对象
            InsuranceContract2.Builder insuranceContract2Bulider = new InsuranceContract2.Builder("CK250429001",
                Convert.ToDateTime(DateTime.Now.ToLongDateString()), Convert.ToDateTime(DateTime.Now.AddDays(1).ToLongDateString()));

            //设置需要的数据,然后构建合同对象
            InsuranceContract2 insuranceContract = insuranceContract2Bulider
                .WithPersonName("张三")
                //.WithCompanyName("XXX科技有限公司")
                .WithOtherData("测试内容")
                .Build();

            //打印合同信息
            insuranceContract.PrintInfo();
        }

    }//Class_end
}

运行结果如下:

三、项目源码工程

kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPattern

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐