【第一节】C++设计模式(创建型模式)-工厂模式

目录

前言

一、面向对象的两类对象创建问题

二、解决问题

三、工厂模式代码示例

四、工厂模式的核心功能

五、工厂模式的应用场景

六、工厂模式的实现与结构

七、工厂模式的优缺点

八、工厂模式的扩展与优化

九、总结


前言

        在面向对象系统设计中,开发者常常面临两类典型的对象创建问题。为了解决这些问题,工厂模式(Factory Pattern)应运而生,成为了一种广泛应用的解决方案。本文将详细探讨这两类问题,并分析工厂模式的功能、实现及其在实际开发中的应用。

一、面向对象的两类对象创建问题

(1)抽象基类与多态带来的问题

        为了提高代码的内聚性(Cohesion)和降低耦合性(Coupling),我们通常会抽象出类的公共接口,形成抽象基类或接口。通过声明指向基类的指针来指向实际子类的实现,从而实现多态性。然而,这种做法也带来了以下问题:
        子类名称依赖:客户端代码必须知道具体子类的名称。随着系统复杂度的增加,命名冲突和可读性问题变得难以处理,尤其是当开发者有不同的命名偏好时。
        扩展性与维护困难:每次使用子类时都需要显式地实例化(如 `new ×××`),导致代码重复,扩展性和维护性变差。

(2)父类无法确定具体子类的问题

        在某些情况下,父类并不知道具体要实例化哪一个子类。例如,假设在类 A 中需要使用类 B,而 B 是一个抽象父类。在类 A 中无法确定具体实例化哪一个 B 的子类,但类 A 的子类 D 可以知道。此时,在类 A 中无法直接使用类似 `new ×××` 的语句,因为具体类型未知。

二、解决问题

        我们通常使用工厂模式(Factory Pattern)来解决上述两个问题。在处理第一个问题时,常见的做法是声明一个创建对象的接口,并将对象的创建过程封装起来。此时,工厂类就像一个真正的“生产工厂”,负责生成所需的对象。

        而在第二个问题中,我们需要提供一个对象创建的接口,并在子类中实现具体的创建逻辑,因为只有子类能够决定实例化哪个具体类。 

第一中情况的 Factory 的结构示意图为:

图1

        图1展示了第一种情况的工厂模式结构示意图。

        这种模式在系统开发中经常被使用,但这并不是工厂模式的最大优势所在(因为这个问题可以通过其他方式解决)。工厂模式不仅仅提供了创建对象的接口,更重要的是它延迟了子类的实例化(即第二个问题)。以下是这种情况的工厂模式结构示意图: 

图2

        图2展示了第二种情况的工厂模式结构示意图。图2中的关键在于,工厂模式的应用并不仅限于封装对象的创建,而是将对象的创建过程放到子类中实现:工厂类只提供对象创建的接口,而具体的实现则由其子类(如 `ConcreteFactory`)完成。这正是图2与图1的主要区别所在。

三、工厂模式代码示例


Product.h

#ifndef PRODUCT_H
#define PRODUCT_H#include <iostream>// 抽象基类 Product
class Product {
public:virtual ~Product() = 0; // 纯虚析构函数
protected:Product() = default; // 默认构造函数,限制为派生类访问
};// 具体派生类 ConcreteProduct
class ConcreteProduct : public Product {
public:ConcreteProduct();~ConcreteProduct() override;
};#endif // PRODUCT_H


Product.cpp

#include "Product.h"// 纯虚析构函数的实现
Product::~Product() = default;// ConcreteProduct 实现
ConcreteProduct::ConcreteProduct() {std::cout << "ConcreteProduct created." << std::endl;
}ConcreteProduct::~ConcreteProduct() {std::cout << "ConcreteProduct destroyed." << std::endl;
}


Factory.h

#ifndef FACTORY_H
#define FACTORY_Hclass Product;// 抽象基类 Factory
class Factory {
public:virtual ~Factory() = 0; // 纯虚析构函数virtual Product* CreateProduct() = 0; // 工厂方法
protected:Factory() = default; // 默认构造函数,限制为派生类访问
};// 具体派生类 ConcreteFactory
class ConcreteFactory : public Factory {
public:ConcreteFactory();~ConcreteFactory() override;Product* CreateProduct() override;
};#endif // FACTORY_H


Factory.cpp

#include "Factory.h"
#include "Product.h"
#include <iostream>// 纯虚析构函数的实现
Factory::~Factory() = default;// ConcreteFactory 实现
ConcreteFactory::ConcreteFactory() {std::cout << "ConcreteFactory created." << std::endl;
}ConcreteFactory::~ConcreteFactory() {std::cout << "ConcreteFactory destroyed." << std::endl;
}Product* ConcreteFactory::CreateProduct() {return new ConcreteProduct();
}


main.cpp

#include "Factory.h"
#include "Product.h"
#include <iostream>int main() {// 创建工厂对象Factory* fac = new ConcreteFactory();// 使用工厂创建产品对象Product* p = fac->CreateProduct();// 释放对象delete p;delete fac;return 0;
}

        在示例代码中,工厂模式(Factory Pattern)被用来解决父类无法确定具体要实例化哪一个子类的问题。至于为创建对象提供接口的问题,可以通过在工厂类中附加相应的创建操作来实现,例如添加 `Create***Product()` 方法。

        工厂模式(Factory Pattern)在实际开发中应用非常广泛,尤其是在面向对象系统中,开发者经常面临对象创建的问题:需要创建的类数量非常多。工厂模式通过提供创建对象的接口封装(第一个功能)以及将类的实例化推迟到子类(第二个功能),部分地解决了这些实际问题。一个典型的例子是笔者在开发 VisualCMCS 系统的语义分析模块时,由于需要为文法中的每个非终结符构造一个处理类,因此对象的创建非常频繁。采用工厂模式后,系统的可读性和维护性都变得更加优雅(elegant)。

        然而,工厂模式也带来至少以下两个问题:  
        (1)接口封闭性问题:如果为每一个具体的 `ConcreteProduct` 类的实例化提供一个单独的函数体,那么我们可能不得不在系统中不断添加新的方法来处理这些新创建的 `ConcreteProduct`。这样,工厂接口就难以做到封闭(Close)。虽然可以通过创建一个工厂的子类来利用多态性实现这一点,但这也会导致需要新增一个类作为代价。  
        (2)参数化工厂方法:在实现中,我们可以通过参数化工厂方法,即给 `FactoryMethod()` 传递一个参数来决定创建哪一个具体的 `Product`(实际上,笔者在 VisualCMCS 中也采用了这种方式)。此外,还可以通过模板化来避免第一个问题中的子类创建,具体方法是将具体的 `Product` 类作为模板参数,实现起来也非常简单。  

        可以看出,工厂模式为对象的创建提供了一种优秀的实现策略。然而,工厂模式仅限于处理同一类别的类(即这些类有一个共同的基类)。如果我们需要为不同类别的类提供一个对象创建的接口,那么就需要使用抽象工厂模式(AbstractFactory)了。抽象工厂模式我们下节再讲。

四、工厂模式的核心功能

工厂模式通过以下两个核心功能,解决了上述问题:
(1)定义创建对象的接口,封装对象的创建过程:工厂模式将对象的创建过程抽象化,客户端代码只需依赖工厂接口,而无需关心具体类的实例化细节。
(2)将具体类的实例化延迟到子类:工厂模式允许子类决定实例化哪个具体类,从而将对象创建的决策推迟到运行时。

五、工厂模式的应用场景

(1)封装对象创建
        在第一个问题中,工厂模式通过声明一个创建对象的接口,封装了对象的创建过程。工厂类类似于一个“生产对象”的工厂,客户端代码只需调用工厂接口,而无需直接依赖具体类。

(2)延迟实例化到子类
        在第二个问题中,工厂模式将具体类的实例化延迟到子类。父类只需定义创建对象的接口,而具体实现则由子类完成。这种方式不仅解决了父类无法确定具体子类的问题,还提高了代码的灵活性和可扩展性。

六、工厂模式的实现与结构

(1)简单工厂模式
        简单工厂模式通过一个工厂类封装对象的创建过程。客户端代码只需调用工厂类的方法,即可获取所需对象。然而,这种模式的扩展性较差,新增产品类型时需要修改工厂类。

(2)工厂方法模式
        工厂方法模式将对象的创建延迟到子类。抽象工厂类定义创建对象的接口,具体工厂类负责实例化具体产品。这种方式符合开闭原则,新增产品类型时只需添加新的工厂类,而无需修改现有代码。

(3)抽象工厂模式
        抽象工厂模式用于创建一系列相关或依赖的对象。它为不同产品族提供创建接口,而具体工厂类负责实例化特定产品族中的对象。这种方式适用于需要创建多个不同类型对象的场景。

七、工厂模式的优缺点

(1)优点
        解耦:将对象创建与使用分离,降低了代码的耦合性。
        扩展性:新增产品类型时无需修改现有代码,符合开闭原则。
        灵活性:通过多态将对象创建延迟到运行时,支持动态决策。

(2)缺点
        类数量增加:每新增一个产品类型,可能需要添加新的工厂类,导致类数量膨胀。
        复杂性增加:工厂模式的实现可能增加系统的复杂性,尤其是抽象工厂模式。

八、工厂模式的扩展与优化

(1)参数化工厂方法
        通过为工厂方法传递参数,决定具体创建哪一个产品。这种方式可以减少工厂类的数量,但可能增加工厂方法的复杂性。

(2)模板化工厂
        将具体产品类作为模板参数,避免为每个产品类型创建新的工厂类。这种方式在 C++ 等支持模板的语言中实现较为简单。

(3)抽象工厂模式
        当需要为不同类的产品提供创建接口时,可以使用抽象工厂模式。抽象工厂模式为每个产品族提供一个创建接口,适用于复杂对象创建的场景。

九、总结

        工厂模式是面向对象设计中解决对象创建问题的经典模式。它通过封装对象创建过程和延迟实例化到子类,有效地降低了代码的耦合性,提高了系统的扩展性和灵活性。尽管工厂模式可能增加类的数量和系统的复杂性,但其在解耦和支持变化方面的优势使其在实际开发中得到了广泛应用。对于需要频繁创建对象的系统,工厂模式无疑是一种优雅且高效的解决方案。

        参考学习书籍:设计模式精解-GoF 23 种设计模式解析

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

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

相关文章

CUDA兼容NVIDA版本关系

CUDA组成 兼容原则 CUDA 驱动(libcuda.so)兼容类型要求比CUDA新向后兼容无主版本一致&#xff0c;子版本旧兼容需要SASS、NVCC比CUDA老向前兼容提取对应兼容包 向后兼容&#xff1a;新版本支持旧版本的内容&#xff0c;关注的是新版本能否处理旧版本的内容。 向前兼容&#…

便捷批量字符一键查找替换工具

软件介绍 今天要给大家分享一款超好用的批量字符替换工具。它操作便捷&#xff0c;只需一键&#xff0c;就能帮你轻松查找替换内容。 这款工具的兼容性堪称强大&#xff0c;支持 txt、reg、bat、cmd、htm、html、xml、asp、aspx、php、php3、php4、php5、jsp、asax、java、cp…

环境变量1

我们今天开始学习环境变量&#xff0c;在此之前我们先接触一下命令行参数是什么 命令行参数 如上图所示&#xff0c;没想到main函数还可以带参数&#xff0c;argc和argv就是命令行参数&#xff0c;argc在C11里面叫可变参数模板&#xff0c;这里同样可变&#xff0c;argc是参数…

【Linux Redis】关于用docker拉取Redis后,让虚拟机运行起来redis,并使得其可以连接到虚拟机外的navicat。

步骤一&#xff1a;拉取Redis镜像 docker pull redis 这个命令会下载最新版本的Redis镜像到你的本地Docker仓库中。你也可以指定一个具体的版本号&#xff0c;例如docker pull redis:6.2.6&#xff0c;来拉取特定版本的Redis镜像。 如果拉取遇到问题请参考【Linux AnolisOS】关…

Android Hal AIDL 简介 (一)

Android 接口定义语言 (AIDL) 是一款可供用户用来抽象化 IPC 的工具。 以在 .aidl 文件中指定的接口为例,各种构建系统都会使用 aidl 二进制文件构造 C++ 或 Java 绑定,以便跨进程使用该接口(无论其运行时环境或位数如何)。 AIDL 可以在 Android 中的任何进程之间使用:在…

《Real-IAD: 用于基准测试多功能工业异常检测的真实世界多视角数据集》学习笔记

paper&#xff1a;2403.12580 GitHub&#xff1a;Real-IAD: A Real-World Multi-View Dataset for Benchmarking Versatile Industrial Anomaly Detection 目录 摘要 1、介绍 2、相关工作 2.1 异常检测数据集 2.2 标准异常检测 2.3 异常检测中的其他设置 3、Real-I…

网络工程师 (47)QOS

一、概念与原理 QOS即服务质量&#xff08;Quality of Service&#xff09;是一种网络技术&#xff0c;用于管理和保证网络中不同类型的质量和性能。它通过设置优先级和带宽限制等策略&#xff0c;确保关键应用&#xff08;如视频会议、语音通信&#xff09;的数据包能够在网络…

总结单例模式的写法(在线程安全的情况下)

目录 1 饿汉模式 2 懒汉模式 3 分析 啥是设计模式? 设计模式好⽐象棋中的 "棋谱". 红⽅当头炮, ⿊⽅⻢来跳. 针对红⽅的⼀些⾛法, ⿊⽅应招的时候有⼀ 些固定的套路. 按照套路来⾛局势就不会吃亏. 软件开发中也有很多常⻅的 "问题场景". 针对这些问题…

首页 layout 架子(element-plus菜单组件)

直接 cv 笔记中静态页面 菜单组件&#xff1a; 认识了解 el-menu整个菜单组件&#xff0c;el-menu-item菜单项&#xff0c;el-sub-menu多级菜单的标题&#xff0c;el-menu-item各个展开内容。也就是一级直接 el-menu-item,如果下面还有分类&#xff0c;就是 el-sub-menu 首页架…

关系中出现这10个信号,离分手就不远了(爱情友情都适用)

亲密关系的隐形裂痕 在一个阳光明媚却略显萧瑟的午后&#xff0c;咖啡杯里的咖啡已经凉透。小李盯着手机屏幕&#xff0c;那些曾经热烈的对话记录现在看起来如此陌生&#xff0c;仿佛隔着一层薄薄的雾。她终于意识到&#xff0c;这段关系已经悄然走向尽头。 亲密关系是一场精心…

CV -- YOLOv8 图像分割(GPU环境)

目录 参考视频&#xff1a; 标注 JSON转为TXT 训练 验证 参考视频&#xff1a; 使用 Yolov8 自定义数据集进行图像分割_哔哩哔哩_bilibili 标注 数据集&#xff1a; 我使用的是一些苹果数据集&#xff0c;可以在我的csdn资源中下载&#xff1a; https://download.csdn.net/do…

从函数到神经网络

所有一切的前提是&#xff0c;你要相信这个世界上的所有逻辑和知识&#xff0c;都可以用一个函数来表示。Functions describe the world ! 比如输入物体的质量和加速度&#xff0c;根据牛顿第二定律&#xff0c;就可以得到物体施加的力&#xff0c;这就是人工智能早期的思路&am…

Token Embedding(词嵌入)和Positional Encoding(位置编码)的矩阵形状关系及转换过程

在从零开始构建一个小型字符级语言模型时,简化的实现步骤是:数据准备→模型架构设计→训练→评估与生成。模型架构设计阶段的流程如下: 图1 模型架构设计阶段的流程 包含了输入层、嵌入层、解码器层和输出层。其中在嵌入层中包括了Token Embedding(词嵌入)和Positional En…

Bigemap Pro如何设置经纬网出图网格设置

第一步&#xff1a;打开bigemap pro软件&#xff0c;单击顶部网格选项第二栏&#xff0c;弹出经纬网设置对话框&#xff0c;如下图&#xff1a; 按作图需求自定义设置后&#xff0c;点击应用如下图&#xff1a; 第二步&#xff1a;设置好经纬网之后&#xff0c;进行作图&#x…

代码辅助工具

爱聚合 爱聚合包含各种AI工具 Fitten Code Fitten Code 小浣熊 小浣熊 Inscode InsCode 可以完整的创建一个应用并实现在线部署。而且可以更新需求一步一步实现代码。

JAVA中常用类型

一、包装类 1.1 包装类简介 java是面向对象的语言&#xff0c;但是八大基本数据类型不符合面向对象的特征。因此为了弥补这种缺点&#xff0c;为这八中基本数据类型专门设计了八中符合面向面向对象的特征的类型&#xff0c;这八种具有面向对象特征的类型&#xff0c;就叫做包…

原生稀疏注意力机制(NSA):硬件对齐且可原生训练的稀疏注意力机制-论文阅读

摘要 长上下文建模对于下一代语言模型至关重要&#xff0c;但标准注意力机制的高计算成本带来了巨大的计算挑战。稀疏注意力提供了一种在保持模型能力的同时提高效率的有前途的方向。本文提出了一种名为 NSA&#xff08;原生可训练稀疏注意力机制&#xff09; 的方法&#xff…

DeepSeek联网搜索

deepseek 0、前言1、未联网2、联网2.1 SerpAPI2.2 SerpAPIDeepseek 0、前言 为获取最新消息&#xff0c;需给deepseek联网 1、未联网 from dotenv import load_dotenv from langchain_deepseek import ChatDeepSeekload_dotenv()# 1、模型 model ChatDeepSeek(model"d…

MySQL 的存储引擎简介

使用show engines可以查看安装的MySQL的引擎相关信息 MySQL 的引擎不止这么多&#xff0c;这只是安装的这个版本有的引擎。可以看到&#xff0c;只有 InnoDB 支持事务&#xff0c;其他的引擎都是不支持事务的。 说几个我了解的比较多的引擎&#xff1a; InnoDB InnoDB 是MyS…

DeepBI助力跨境电商打破流量垄断:AI驱动的亚马逊广告投放新打法

#亚马逊广告优化# 亲爱的亚马逊跨境电商卖家们&#xff0c;是否曾因亚马逊的广告打法不清晰&#xff0c;或是纠结于亚马逊广告费用过高&#xff0c;或是为亚马逊电商广告怎么投放合适的问题而苦恼&#xff1f;在竞争激烈的亚马逊市场中&#xff0c;广告投放效果平平&#xff0…