设计模式精讲:掌握工厂方法与抽象工厂的精髓

设计模式精讲:掌握工厂方法与抽象工厂的精髓

  • 一、引言:如何学习设计模式?
  • 二、工厂方法(也叫工厂模式)
    • 2.1、代码结构
    • 2.2、符合的设计原则
    • 2.3、小结
  • 三、抽象工厂
    • 3.1、代码结构
    • 3.2、符合的设计原则
    • 3.3、小结
  • 总结

一、引言:如何学习设计模式?

学习设计模式最主要要抓住一点:就是怎么分析这个稳定点和变化点。自己实现一个框架,或者是实现一个具体的小功能,本质上分析问题的思路都是一样的,首先要去把稳定点给它抽象出来,然后针对这个变化点想着怎么去扩展它。所以这里还是要反复的介绍怎么分析这个稳定点和变化点;具体不同的设计模式是怎么来处理这个扩展(就是扩展的问题);稳定点它是怎么处理的;用C++的语言特性是怎么去解决这些问题的;沿着这个思路去学习。

前面已经介绍了设计模式当中的模板方法、观察的模式、以及策略模式,这里再次强调以下学习、掌握设计模式的学习步骤。

  • 首先,需要来了解设计模式解决了什么问题。本质上是分析它的稳定点和变化点,实际在做具体功能开发的时候也需要去抽象具体的稳定点以及想办法去扩展变化点,这样在实际开发过程当中,尽量写少量的代码去应对未来需求的变化。
  • 第二点,设计模式的代码结构是什么。需要培养一个看代码、看一些框架或者看项目代码结构的时候马上能够反应出来使用了什么设计模式,或者它符合什么设计原则,从而可以推断出代码具体的意图。熟悉实现具体设计模式的代码结构能够帮助我们对一个代码有一个敏感度,以便能够快速的进行推断和反应。
  • 第三点,看这些设计模式符合了哪些设计原则。因为设计模式是由设计原则推导过来的,所以可以按照这一个设计模式的产生的流程重新去思考这一个问题,能够帮助我们去很好的去设计我们的代码。相信很多人在具体的工作当中都有自己不同的一些设计方式,它不一定符合某一些设计模式,未来大家应对的某些需求也会自己去设计一个框架,所以可以思考它符合哪些设计原则。
  • 第四点,如何在上面扩展代码。尤其是对于初学者或刚刚参加工作的朋友们,对这个扩展代码一定要非常的清楚,就是如果在这个设计模式的基础上要修改哪些代码,这个是必须要掌握的。
  • 第五点,按照自己的需求或者自己的项目以及自己的工作场景进行一个联系,哪些需求变化可以使用设计模式;在看开源框架的时候也可以去看一下它是怎么解决这一个问题的。记住几个关键设计模式的一些典型应用场景能够帮助我们快速的反应;当具体需求来了知道该怎么使用某一些设计模式。
学习步骤
设计模式解决什么问题
稳定点
变化点
设计模式的代码结构是什么
设计模式符合哪些设计原则
如何在上面扩展代码
该设计模式有哪些典型应用场景
联系工作场景
开源框架

这个就是设计模式具体的学习步骤。

二、工厂方法(也叫工厂模式)

首先从定义出发,工厂方法的定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使得一个类的实例化延迟到子类

接下来就要分析它解决了什么问题。它的定义不需要特别的去关注,也不要记这个定义,主要分析这个定义当中的稳定点和变化点。

  • 稳定点:创建同类对象的接口,并且同类对象有一个相同的职责。要提供同类对象的一个接口,同类对象只有一个相同的职责(职责就是功能的意思)。
  • 变化点:对象的拓展。同一类对象会越来越多,要进行对象的扩展。

2.1、代码结构

举个例子来帮助理解:

实现一个导出数据的接口,让客户选择数据的导出方式。

这个的职责是导出数据,这里面的导出数据有很多方式可以让用户选择,即同类对象都有导出功能,而且都是导出数据。因此,要创建一个对象接口以满足同类对象有相同的职责(职责就是导出数据)。

不使用设计模式的时候,首先要使用设计原则,注意到的是“导出数据”是一个职责,马上要想到接口,对于这种问题想都不用想就是要考虑用接口去封装它,因为有多个不同导出数据的接口,就要用统一的接口,这个接口就代表它有一个什么样的功能(即导出功能),马上就可以写出这样的一个语句:

// 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv
class IExport {
public:virtual bool Export(const std::string &data) = 0;virtual ~IExport(){}
};

就是导出这个具体的数据有很多格式,比如说有xml,有json,有txt,还有excel格式csv等等。然后尝试实现接口:继承IExport ,实现Export()方法。

#include <string>
// 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv
class IExport {
public:virtual bool Export(const std::string &data) = 0;virtual ~IExport(){}
};class ExportXml : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};class ExportJson : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};
// csv
class ExportTxt : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};// class ExportCSV : public IExport {
// public:
//     virtual bool Export(const std::string &data) {
//         return true;
//     }
// };// =====1
int main() {std::string choose/* = */;if (choose == "txt") {/***/IExport *e = new ExportTxt();/***/e->Export("hello world");} else if (choose == "json") {/***/IExport *e = new ExportJson();/***/e->Export("hello world");} else if (choose == "xml") {IExport *e = new ExportXml();e->Export("hello world");} else if (choose == "csv") {IExport *e = new ExportXml();e->Export("hello world");}
}

实现导出功能,实现IExport类就可以导出为xml、json、txt等格式,因为在这里只有一个导出职责(导出功能),都是一样的,同样的接口Export把它实现好,未来可能会增加CSV也可以实现一下。接下来让用户选择数据的导出方式,int main() 就要实现这个功能了。在这里按用户的输入判断,然后new出来这个数据,然后再把它进行Export。这个是没有使用设计模式的时候,仅仅考虑面向接口编程写出来的这样的一个代码。

接下来看一下符合设计模式是怎么进行开发的,其实这里创建同类对象的接口可以用一个工厂接口来实现,有相同的职责可以用一个具体的职责接口来实现,也就是说这里应该要抽象出两个接口:一个是对象产生的接口,一个是具体的职责的接口。要理解这句话,先思考一个问题:

为什么要有工厂模式?

再来了解一下为什么在这里面要用两个接口,而不直接new对象来使用?刚刚举的例子,有一个业务可能没注意到,主要的原因是没有表现出来,因为通常如果使用模式的时候,new的对象不是直接用一个对象这么简单,new完之后还要去有复杂的构造流程。

设计模式通常是要解决创建过程比较复杂,而且希望对外隐藏这些细节的场景,也就是说某一个用户只使用需要使用的功能。比如说导出JSON数据除了要去把JSON库导出来(加载JSON库)可能还需要去调用一些配置参数(配置这个JSON的参数),还可能要进行初始化一些数据,把这些流程都封装起来,放到某一个对象的JSON导出的接口中,那么对于用户而言,只需要知道用JSON导出,用户根本不需要关注还要加载一个库、配置一些参数、初始化一些值才能够去使用它的导出功能。

当创建过程比较复杂,而用户只需要关注他的职责,并不关注他的创建过程,比较复杂的这个流程用户不需要知道,这种时候需要使用工厂方法模式。举个例子,比如说连接池、线程池等,使用连接池通常只需要把一个任务push进去就行了,对于用户而言,只需要有一个push的操作就行了,往里面去push后会自动帮用户完成;如果真正把线程池封装的好,那么对于用户而言,应该只需要知道有一个push接口就行了。都知道线程池要去构建它要初始化(这个把线程加载进来,把线程初始化好),把一些数据初始化,然后把一些入口函数初始化等等,这里面有复杂的构造流程,这些构造流程对于用户而言没什么用,用户根本不需要关注这些东西,用户只需要push任务,对于一个正确的封装者(做功能开发、做功能抽象的人)应该是让用户知道的越少越好。

要点:解决创建过程比较复杂,希望对外隐藏这些细节的场景。

  • 比如连接池、线程池
  • 隐藏对象真实类型;
  • 对象创建会有很多参数来决定如何创建;
  • 创建对象有复杂的依赖关系。

工厂方法实现的代码:

#include <string>
// 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv
class IExport {
public:virtual bool Export(const std::string &data) = 0;virtual ~IExport(){}
};class ExportXml : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};class ExportJson : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};class ExportTxt : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};class ExportCSV : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};class IExportFactory {
public:IExportFactory() {_export = nullptr;}virtual ~IExportFactory() {if (_export) {delete _export;_export = nullptr;}}bool Export(const std::string &data) {if (_export == nullptr) {_export = NewExport();}return _export->Export(data);}
protected:virtual IExport * NewExport(/* ... */) = 0;
private:IExport* _export;
};class ExportXmlFactory : public IExportFactory {
protected:virtual IExport * NewExport(/* ... */) {// 可能有其它操作,或者许多参数IExport * temp = new ExportXml();// 可能之后有什么操作return temp;}
};
class ExportJsonFactory : public IExportFactory {
protected:virtual IExport * NewExport(/* ... */) {// 可能有其它操作,或者许多参数IExport * temp = new ExportJson;// 可能之后有什么操作return temp;}
};
class ExportTxtFactory : public IExportFactory {
protected:IExport * NewExport(/* ... */) {// 可能有其它操作,或者许多参数IExport * temp = new ExportTxt;// 可能之后有什么操作return temp;}
};class ExportCSVFactory : public IExportFactory {
protected:virtual IExport * NewExport(/* ... */) {// 可能有其它操作,或者许多参数IExport * temp = new ExportCSV;// 可能之后有什么操作return temp;}
};int main () {IExportFactory *factory = new ExportCSVFactory();factory->Export("hello world");return 0;
}

为了实现封装出复杂的对象的构建过程,给了一个对象创建的一个接口IExportFactory ,那么这里的IExport 是功能接口(具体到底实现了一个什么功能)。具体的构造流程在NewExport()中实现,用户不会关注这个的实现,用户只关注Export功能(导出功能)。知道这些接口之后,还需要不同对象的构造,IExportFactory 是基类,使用ExportTxtFactory ExportCSVFactory ExportXmlFactory 等实现不同的导出对象,复杂的操作流程就在NewExport中实现。最后就是在main ()中使用它。

代码结构:

  • 对象创建接口。
  • 功能接口。
  • 对象扩展。
  • 多态调用的使用方式。

2.2、符合的设计原则

(1)最小知道原则。
(2)面向接口原则。

用户知道的越少越好,让用户只需要知道最需要的东西就行;想让用户知道的越少就要面向接口编程,把用户只关注的行为抽象成接口。

需要哪些接口?

  1. 具体的职责类的功能接口。简单来说就是提供了一个什么功能给用户,示例中用户只需要有一个导出的功能,只要给用户这样一个导出接口,用户只关注一个导出接口就行了,即功能接口是做什么事情的用户要知道。
  2. 对象创建接口。虽然用户不关注,但是要知道怎么去创建;就是有一个具体的对象,到底用户应该用什么东西来实现这个功能,比如说是用JSON来实现这个导出功能、用txt来实现这个导出功能、用CSV来现这个导出功能,就需要一个对象创建的接口;这个对象创建的接口就是所说的工厂接口。

2.3、小结

稳定点都是用抽象去解决,抽象那些万成不变的东西,具体可以用接口来实现。通过分析稳定点,发现要有一个创建同类对象的一个职责接口以及一个功能接口,这两个东西是稳定点,需要注意这个功能接口对于用户而言是不可见的,用户不需要关注这一个功能接口,这个功能接口它具体起到一个什么作用可以看到示例中IExportFactory的功能对象创建的流程。IExportFactory只提供了Export,并没有提供创建的流程,创建流程在NewExport()实现。

扩展代码:

  • 实现对象创建接口。
  • 实现功能接口。
  • 多态调用。

本质:延迟到子类来选择实现。

结构图:

Factory
+CreateProduct()
Product
+Operator()
ConcreteProduct1
+Operator()
ConcreteProduct2
+Operator()
ConcreteProduct3
+Operator()

思维导图:
在这里插入图片描述

三、抽象工厂

理解了工厂模式,抽象工厂就非常的简单,它其实就是在工厂模式当中增加了一个多职责,它除了创建同类对象的接口,它还有就是有多个相同的职责。

抽象工厂的定义:提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。

解决了什么问题:

  • 稳定点:创建同类对象的接口,并且同类对象有多个相同的职责。和工厂方法模式唯一的区别是"有多个相同的职责"。
  • 变化点:对象的拓展。和工厂方法模式一样

3.1、代码结构

看一个例子:

实现一个拥有导出导入数据的接口,让客户选择数据的导出导入方式。

跟前面的工厂方法中的例子差不多,只是多了一个导入的功能,并且让用户去选择导入的方式。也就是说某一个对象(比如说CSV),它刚刚只需要有一个导出功能,现在还要有一个导入的功能;同样的,要把这个导入功能职责也要抽象成一个接口。

这个抽象工厂就非常的简单,它的代码结构基本和工厂方法一样的:

  • 对象创建接口。包括创建具体对象,提供多个功能接口来调用。在创建对象接口当中去调用功能接口。
  • 多个功能接口。

抽象工厂在工厂方法上面就多了一个,它是有多个职责的,它有多个功能的。

抽象工厂的代码实现:

#include <string>
// 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv
class IExport {
public:virtual bool Export(const std::string &data) = 0;virtual ~IExport(){}
};class ExportXml : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};class ExportJson : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};class ExportTxt : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};class ExportCSV : public IExport {
public:virtual bool Export(const std::string &data) {return true;}
};class IImport {
public:virtual bool Import(const std::string &data) = 0;virtual ~IImport(){}
};class ImportXml : public IImport {
public:virtual bool Import(const std::string &data) {return true;}
};class ImportJson : public IImport {
public:virtual bool Import(const std::string &data) {return true;}
};class ImportTxt : public IImport {
public:virtual bool Import(const std::string &data) {return true;}
};class ImportCSV : public IImport {
public:virtual bool Import(const std::string &data) {// ....return true;}
};class IDataApiFactory {
public:IDataApiFactory() {_export = nullptr;_import = nullptr;}virtual ~IDataApiFactory() {if (_export) {delete _export;_export = nullptr;}if (_import) {delete _import;_import = nullptr;}}bool Export(const std::string &data) {if (_export == nullptr) {_export = NewExport();}return _export->Export(data);}bool Import(const std::string &data) {if (_import == nullptr) {_import = NewImport();}return _import->Import(data);}
protected:virtual IExport * NewExport(/* ... */) = 0;virtual IImport * NewImport(/* ... */) = 0;
private:IExport *_export;IImport *_import;
};class XmlApiFactory : public IDataApiFactory {
protected:virtual IExport * NewExport(/* ... */) {// 可能有其它操作,或者许多参数IExport * temp = new ExportXml;// 可能之后有什么操作return temp;}virtual IImport * NewImport(/* ... */) {// 可能有其它操作,或者许多参数IImport * temp = new ImportXml;// 可能之后有什么操作return temp;}
};class JsonApiFactory : public IDataApiFactory {
protected:virtual IExport * NewExport(/* ... */) {// 可能有其它操作,或者许多参数IExport * temp = new ExportJson;// 可能之后有什么操作return temp;}virtual IImport * NewImport(/* ... */) {// 可能有其它操作,或者许多参数IImport * temp = new ImportJson;// 可能之后有什么操作return temp;}
};
class TxtApiFactory : public IDataApiFactory {
protected:virtual IExport * NewExport(/* ... */) {// 可能有其它操作,或者许多参数IExport * temp = new ExportTxt;// 可能之后有什么操作return temp;}virtual IImport * NewImport(/* ... */) {// 可能有其它操作,或者许多参数IImport * temp = new ImportTxt;// 可能之后有什么操作return temp;}
};class CSVApiFactory : public IDataApiFactory {
protected:virtual IExport * NewExport(/* ... */) {// 可能有其它操作,或者许多参数IExport * temp = new ExportCSV;// 可能之后有什么操作return temp;}virtual IImport * NewImport(/* ... */) {// 可能有其它操作,或者许多参数IImport * temp = new ImportCSV;// 可能之后有什么操作return temp;}
};// 相关性  依赖性    工作当中
int main () {IDataApiFactory *factory = new CSVApiFactory();factory->Import("hello world");factory->Export("hello world");return 0;
}

除了导出接口,还加入了导入接口,分别实现它们的职责。

3.2、符合的设计原则

基本和工厂方法的一样。
(1)最小知道原则。
(2)面向接口原则。

3.3、小结

抽象工厂模式和工厂方法的区别:工厂方法通常一个对象只有一个职责,而抽象工厂模式是一个对象有多个职责

结构图:
在这里插入图片描述
思维导图:
在这里插入图片描述

总结

在本文中,我们深入探讨了设计模式中两个重要的概念:工厂方法和抽象工厂。首先介绍了设计模式的重要性和应用场景,然后重点讲解了工厂方法模式和抽象工厂模式的原理和实现方式。通过详细的代码结构和实例分析,我们揭示了它们如何提供灵活性、可扩展性和可维护性,并如何符合设计原则。最后,我们强调了工厂方法和抽象工厂的精髓,帮助读者深入理解并将其应用于实际项目中,从而提高编程技能和代码质量。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/236844.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PostgreSQL-shared_buffers(双缓存)

关于shared_buffers 这是一篇2018年写的&#xff0c;可以结合shared read一起看 什么是shred_buffer&#xff0c;我们为什么需要shared_buffers&#xff1f; 1.在数据库系统中&#xff0c;我们主要关注磁盘io&#xff0c;大多数oltp工作负载都是随机io&#xff0c;因此从磁盘获…

zabbix配置snmp trap--使用snmptrapd和Bash接收器(图文教程)

1.前言 我的zabbix的版本是5.0版本&#xff0c;5.0的官方文档没有使用bash接收器的示例&#xff0c;6.0的官方文档有使用bash接收器的示例&#xff0c;但是&#xff0c;下载文件的链接失效&#xff1f;&#xff01; 这里讲解zabbix-server端配置和zabbix web端配置 2.zabbix-…

基于YOLOv8深度学习的安全帽目标检测系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

C51--DHT11数据读取

DHT11传输0的时序分析&#xff1a; DHT11传输1的时序分析&#xff1a; 用while(dht)卡点&#xff0c;当不满足while时&#xff0c;信号拉低&#xff1b; 用while(&#xff01;dht)卡点&#xff0c;当不满足while时&#xff0c;信号拉高。 传输0和1时有效数据都是高电平&…

【数据结构】—AVL树(C++实现)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 &#x1f49c;本文前置知识&#xff1a; 搜索二叉树 ♈️今日夜电波&#xff1a;Letter Song—ヲタみん 1:36━━━━━━️&#x1f49f;──────── 5:35 …

C 语言超全练习题(一): 初识C语言

文章目录 一、问答题1.1 C语言开发过程是怎样的&#xff1f;1.2 C语言的应用领域有哪些&#xff1f; 二、上机题2.1 第一个C语言程序2.2 一个完整的C语言程序2.3 输出名言2.4 计算出正方形的周长 一、问答题 1.1 C语言开发过程是怎样的&#xff1f; 问题描述&#xff1a;C语言…

10kv配电室无人值守系统

10kv配电室无人值守系统是一种自动化控制系统&#xff0c;依托电易云-智慧电力物联网可以实现对配电室的监测、控制、保护和故障诊断等功能&#xff0c;从而实现配电室的无人值守。该系统主要由控制器、传感器、执行机构、通信模块等组成&#xff0c;可以实现对电力设备的自动化…

【Windows】内网穿透实现hMailServer远程发送邮件

目录 前言1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 前言 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpolar内网…

mongoDB非关系型数据库学习记录

一、简介 1.1Mongodb是什么 MongoDB是一个基于分布式文件存储的数据库,官方地址https://www.mongodb.com/ 1.2数据库是什么 数据库(DataBase)是按照数据结构来组织、存储和管理数据的应用程序 1.3数据库的作用 数据库的主要作用就是管理数据,对数据进行增©、删(d)、…

会员管理系统的意义何在?

在当今的商业环境中&#xff0c;会员管理系统已经成为企业运营的重要组成部分。会员管理系统的意义在于不仅能够帮助企业提高效率&#xff0c;提升用户体验&#xff0c;进行数据分析&#xff0c;营销推广&#xff0c;还能够帮助企业增加收入。下面&#xff0c;我们将详细探讨会…

【HTTP协议】简述HTTP协议的概念和特点

&#x1f38a;专栏【网络编程】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 &#x1f970;欢迎并且感谢大家指出小吉的问题 文章目录 &#x1f33a;概念&#x1f33a;特点&#x1f384;请求协议&#x1f384;响应协议…

Everything结合内网穿透搭建在线资料库并实现随时随地远程访问

Everythingcpolar搭建在线资料库&#xff0c;实现随时随地访问 文章目录 Everythingcpolar搭建在线资料库&#xff0c;实现随时随地访问前言1.软件安装完成后&#xff0c;打开Everything2.登录cpolar官网 设置空白数据隧道3.将空白数据隧道与本地Everything软件结合起来总结 前…