ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 生成器模式 ## 导出数据的应用框架 对于导出数据的应用框架,通常在导出数据上,有一定约定的方式,比如导出成文本格式、数据库备份形式、Excel格式、Xml格式等 导出内容和格式有如下要求: * 导出的文件,不管什么格式,都分成3个部分,分别是文件头、文件体和文件尾 * 在文件头部分,需要描述以下信息:分公司或者门市点编号、导出数据的日期,对于文本文件,中间用逗号分隔 * 在文本体部分,需要描述以下信息:表名称,然后分条描述数据。对于文本格式,表名称单独一行,数据描述一行算一条数据,字段间用逗号分隔 * 在文件尾部分,需要描述以下信息:输入人 为演示简单,这里只实现生成文本文件格式和Xml格式,其他不考虑。 ## 不用模式的解决方案 ```cpp #include <iostream> #include <memory> #include <unordered_map> #include <vector> /** * 描述输出文件头的内容的对象 */ class ExportHeaderModel { public: std::string getDepId() const { return depId_; } void setDepId(const std::string& depId) { depId_ = depId; } std::string getExportDate() const { return exportDate_; } void setExportDate(const std::string& exportDate) { exportDate_ = exportDate; } private: std::string depId_; // 分公司或门市店的编号 std::string exportDate_; // 导出数据的日期 }; /** * 描述输出数据的对象 */ class ExportDataModel { public: std::string getProductId() const { return productId_; } void setProductId(const std::string& productId) { productId_ = productId; } double getPrice() const { return price_; } void setPrice(double price) { price_ = price; } double getAmount() const { return amount_; } void setAmount(double amount) { amount_ = amount; } private: std::string productId_; // 产品编号 double price_; // 销售价格 double amount_; // 销售数量 }; /** * 描述输出到文件尾的内容的对象 */ class ExportFooterModel { public: std::string getExportUser() const { return exportUser_; } void setExportUser(const std::string& exportUser) { exportUser_ = exportUser; } private: std::string exportUser_; // 输入人 }; /** * 导出数据到文本文件的对象 */ class ExportToTxt { public: /** * 描述输出数据的对象 */ void exportFile(const ExportHeaderModel& ehm, const std::unordered_map<std::string, std::vector<ExportDataModel> >& mapData, const ExportFooterModel& efm) { std::string buffer; buffer.append(ehm.getDepId() + "," + ehm.getExportDate() + "\n"); for (auto& it : mapData) { buffer.append(it.first + "\n"); for (auto& edm : it.second) { buffer.append(edm.getProductId() + "," + std::to_string(edm.getPrice()) + "," + std::to_string(edm.getAmount()) + "\n"); } } buffer.append(efm.getExportUser()); std::cout << buffer << std::endl; } }; /** * 导出数据到文本文件的对象 */ class ExportToXML { public: /** * 描述输出数据的对象 */ void exportFile(const ExportHeaderModel& ehm, const std::unordered_map<std::string, std::vector<ExportDataModel> >& mapData, const ExportFooterModel& efm) { std::string buffer; buffer.append("<?xml version='1.0' encoding='gb2312'?>\n"); buffer.append("<Report>\n"); buffer.append(" <Header>\n"); buffer.append(" <DepId>" + ehm.getDepId() + "</DepId>\n"); buffer.append(" <ExportDate>" + ehm.getExportDate() + "</ExportDate>\n"); buffer.append(" </Header>\n"); buffer.append(" <Body>\n"); for (auto& it : mapData) { buffer.append(" <Datas TableName=\"" + it.first + "\">\n"); for (auto& edm : it.second) { buffer.append(" <Data>\n"); buffer.append(" <ProductId>" + edm.getProductId() + "</ProductId>\n"); buffer.append(" <Price>" + std::to_string(edm.getPrice()) + "</Price>\n"); buffer.append(" <Amount>" + std::to_string(edm.getAmount()) + "</Amount>\n"); buffer.append(" </Data>\n"); } buffer.append(" </Datas>\n"); } buffer.append(" </Body>\n"); buffer.append(" <Footer>\n"); buffer.append(" <ExportUser>" + efm.getExportUser() + "</ExportUser>\n"); buffer.append(" </Footer>\n"); buffer.append("</Report>\n"); std::cout << buffer << std::endl; } }; void test() { ExportHeaderModel ehm; ehm.setDepId("一分公司"); ehm.setExportDate("2020-07-30"); std::unordered_map<std::string, std::vector<ExportDataModel> > mapData; std::vector<ExportDataModel> col; ExportDataModel edm1; edm1.setProductId("产品001号"); edm1.setPrice(100); edm1.setAmount(80); ExportDataModel edm2; edm2.setProductId("产品002号"); edm2.setPrice(99); edm2.setAmount(55); col.push_back(edm1); col.push_back(edm2); mapData["销售记录表"] = col; ExportFooterModel efm; efm.setExportUser("张三"); std::cout << "输出内容到txt文件格式:" << std::endl; ExportToTxt toTxt; toTxt.exportFile(ehm, mapData, efm); std::cout << std::endl << "输出内容到txt文件格式:" << std::endl; ExportToXML toXml; toXml.exportFile(ehm, mapData, efm); } int main(int argc, char** argv) { test(); return 0; } ``` 运行结果: ``` 输出内容到txt文件格式: 一分公司,2020-07-30 销售记录表 产品001号,100.000000,80.000000 产品002号,99.000000,55.000000 张三 输出内容到txt文件格式: <?xml version='1.0' encoding='gb2312'?> <Report> <Header> <DepId>一分公司</DepId> <ExportDate>2020-07-30</ExportDate> </Header> <Body> <Datas TableName="销售记录表"> <Data> <ProductId>产品001号</ProductId> <Price>100.000000</Price> <Amount>80.000000</Amount> </Data> <Data> <ProductId>产品002号</ProductId> <Price>99.000000</Price> <Amount>55.000000</Amount> </Data> </Datas> </Body> <Footer> <ExportUser>张三</ExportUser> </Footer> </Report> ``` ## 不用模式方案问题 观察以上实现,发现不管是输出文本文件还是输出XML文件,实现的基本基本一致,分为以下四步: 1. 先拼接文件头的内容 2. 然后拼接文件体的内容 3. 在拼接文件尾的内容 4. 最后把拼接好的内容输出去称为文件 也就是说,对于不同的输出格式,处理步骤是一样的,但是每步的具体实现是不一样的。按照现在的实现,就存在如下问题: * 构建每种输出格式的文件时,都会重复这几个步骤,应该提炼出来,形成公共的处理过程 * 今后可能有很多不同的输出格式的要求,这就需要在处理过程不变的情况下,能方便地切换不同的输出格式的处理 也就是说,构建每种格式的数据文件的处理过程,应该和具体的步骤实现分开,这样能够复用处理过程,而且很容易地切换不同的输出格式。 ## 生成器模式解决方案 ### 生成器模式定义 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 ```cpp #include <iostream> #include <memory> #include <unordered_map> #include <vector> /** * 描述输出文件头的内容的对象 */ class ExportHeaderModel { public: std::string getDepId() const { return depId_; } void setDepId(const std::string& depId) { depId_ = depId; } std::string getExportDate() const { return exportDate_; } void setExportDate(const std::string& exportDate) { exportDate_ = exportDate; } private: std::string depId_; // 分公司或门市店的编号 std::string exportDate_; // 导出数据的日期 }; /** * 描述输出数据的对象 */ class ExportDataModel { public: std::string getProductId() const { return productId_; } void setProductId(const std::string& productId) { productId_ = productId; } double getPrice() const { return price_; } void setPrice(double price) { price_ = price; } double getAmount() const { return amount_; } void setAmount(double amount) { amount_ = amount; } private: std::string productId_; // 产品编号 double price_; // 销售价格 double amount_; // 销售数量 }; /** * 描述输出到文件尾的内容的对象 */ class ExportFooterModel { public: std::string getExportUser() const { return exportUser_; } void setExportUser(const std::string& exportUser) { exportUser_ = exportUser; } private: std::string exportUser_; // 输入人 }; /** * 生成器接口,定义创建一个输出文件对象所需要各个部件的操作 */ class Builder { public: /** * 构建输出文件的Header部分 */ virtual void buildHeader(const ExportHeaderModel& ehm) = 0; /** * 构建输出文件的Body部分 */ virtual void buildBody(const std::unordered_map<std::string, std::vector<ExportDataModel> >& mapData) = 0; /** * 构建输出文件的Footer部分 */ virtual void buildFooter(const ExportFooterModel& efm) = 0; /** * 返回结果 */ std::string getResult() const { return buffer; } protected: std::string buffer; }; /** * 实现导出数据到文本文件的生成器对象 */ class TxtBuilder : public Builder { public: void buildHeader(const ExportHeaderModel& ehm) { buffer.append(ehm.getDepId() + "," + ehm.getExportDate() + "\n"); } void buildBody(const std::unordered_map<std::string, std::vector<ExportDataModel> >& mapData) { for (auto& it : mapData) { buffer.append(it.first + "\n"); for (auto& edm : it.second) { buffer.append(edm.getProductId() + "," + std::to_string(edm.getPrice()) + "," + std::to_string(edm.getAmount()) + "\n"); } } } void buildFooter(const ExportFooterModel& efm) { buffer.append(efm.getExportUser()); } }; /** * 实现导出数据到XML文件的生成器对象 */ class XmlBuilder : public Builder { public: void buildHeader(const ExportHeaderModel& ehm) { buffer.append("<?xml version='1.0' encoding='gb2312'?>\n"); buffer.append("<Report>\n"); buffer.append(" <Header>\n"); buffer.append(" <DepId>" + ehm.getDepId() + "</DepId>\n"); buffer.append(" <ExportDate>" + ehm.getExportDate() + "</ExportDate>\n"); buffer.append(" </Header>\n"); } void buildBody(const std::unordered_map<std::string, std::vector<ExportDataModel> >& mapData) { buffer.append(" <Body>\n"); for (auto& it : mapData) { buffer.append(" <Datas TableName=\"" + it.first + "\">\n"); for (auto& edm : it.second) { buffer.append(" <Data>\n"); buffer.append(" <ProductId>" + edm.getProductId() + "</ProductId>\n"); buffer.append(" <Price>" + std::to_string(edm.getPrice()) + "</Price>\n"); buffer.append(" <Amount>" + std::to_string(edm.getAmount()) + "</Amount>\n"); buffer.append(" </Data>\n"); } buffer.append(" </Datas>\n"); } buffer.append(" </Body>\n"); } void buildFooter(const ExportFooterModel& efm) { buffer.append(" <Footer>\n"); buffer.append(" <ExportUser>" + efm.getExportUser() + "</ExportUser>\n"); buffer.append(" </Footer>\n"); buffer.append("</Report>\n"); } }; /** * 指导者,指导使用生成器的接口来构建输出的文件的对象 */ class Director { public: /** * 构造方法,传入生成器对象 */ Director(std::shared_ptr<Builder> builder) : builder_(builder) {} /** * 指导生成器构建最终的输出的文件的方法 * @param ehm 文件头的内容 * @param mapData 数据的内容 * @param efm 文件尾的内容 */ void construct(const ExportHeaderModel& ehm, const std::unordered_map<std::string, std::vector<ExportDataModel> >& mapData, const ExportFooterModel& efm) { builder_->buildHeader(ehm); builder_->buildBody(mapData); builder_->buildFooter(efm); } private: std::shared_ptr<Builder> builder_; //持有当前需要使用的生成器对象 }; void test() { ExportHeaderModel ehm; ehm.setDepId("一分公司"); ehm.setExportDate("2020-07-30"); std::unordered_map<std::string, std::vector<ExportDataModel> > mapData; std::vector<ExportDataModel> col; ExportDataModel edm1; edm1.setProductId("产品001号"); edm1.setPrice(100); edm1.setAmount(80); ExportDataModel edm2; edm2.setProductId("产品002号"); edm2.setPrice(99); edm2.setAmount(55); col.push_back(edm1); col.push_back(edm2); mapData["销售记录表"] = col; ExportFooterModel efm; efm.setExportUser("张三"); // txt格式 std::shared_ptr<Builder> txtBuilder_ptr(new TxtBuilder()); Director txt_director(txtBuilder_ptr); txt_director.construct(ehm, mapData, efm); std::cout << "输出内容到txt文件格式:" << std::endl; std::cout << txtBuilder_ptr->getResult() << std::endl << std::endl; // XML格式 std::shared_ptr<Builder> xmlBuilder_ptr(new XmlBuilder()); Director xml_director(xmlBuilder_ptr); xml_director.construct(ehm, mapData, efm); std::cout << "输出内容到XML文件格式:" << std::endl; std::cout << xmlBuilder_ptr->getResult() << std::endl; } int main(int argc, char** argv) { test(); return 0; } ``` 运行结果和上面一样 上面的示例其实就是生成器模式,其实挺简单。对同一个构建过程,只要配置不同的生成器实现,就可以生成不同表现的对象,就是生成器模式的实现方式和优势所在。 ## 生成器模式的本质 **分离整体构建算法和部件构造** 生成器模式的构建过程是统一的、固定不变的,变化的部分放到程程器部分,只要配置不同的生成器,那么同样的构建过程,就能构建出不同的产品来。