当前位置: 首页 > news >正文

C++设计模式_08_Factory Method工厂方法模式

文章目录

  • 1. “对象创建模式”模式
    • 1.1 典型模式
  • 2. 动机(Motivation)
  • 3. 代码演示Factory Method工厂方法模式
    • 3.1 常规方法
    • 3.2 面向接口的编程
      • 3.2.1 FileSplitter1.cpp
      • 3.2.2 MainForm1.cpp
    • 3.3 Factory Method工厂方法
      • 3.3.1 ISplitterFactory.cpp
      • 3.3.2 MainForm2.cpp
      • 3.3.3 FileSplitter2.cpp
  • 4. 模式定义
  • 5. 结构
  • 6. 要点总结
  • 7. 其他参考

本篇将会介绍Factory Method工厂方法模式,其属于一个新的类别,将其归结到“对象创建模式”,该模式的简介如下:

1. “对象创建模式”模式

通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。

1.1 典型模式

  • Factory Method
  • Abstract Factory
  • Prototype
  • Builder

这四个模式非常接近,解决的是同一个问题,只不过这些问题在演化过程中会有细微的差别,需要四个不同的模式进行应对。

2. 动机(Motivation)

  • 在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。

  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?

对C++设计模式_05_Observer 观察者模式中的文件分割器的代码抽象,将观察者模式等跟本篇介绍内容不相关的去除,只突出跟“对象创建模式”相关的代码。

3. 代码演示Factory Method工厂方法模式

3.1 常规方法

常规的方法是,创建一个Splitter类,在类中定义一个核心的split方法,在客户端收集参数之后,创建一个对象,通过对象调用方法完成分割。

这种方法存在什么问题呢?

假如我们在一个变化场景看问题,一般意义上,一个类型,需要看到未来变化的需求,这个时候就需要做抽象类或者接口,这是我们最早讲的设计原则-面向接口的编程

3.2 面向接口的编程

面向接口的编程告诉我们,以一个对象的类型,往往应该声明为一个抽象类或者接口,而不应该声明为具体的类,一旦声明为具体的类,就意味着没有支持未来的变化。

假设上面的代码中只支持二进制文件的分割,但是未来也可能支持文本文件的分割或者图片文件或视频文件的分割等。那么代码就变成如下所示:

3.2.1 FileSplitter1.cpp

class ISplitter{
public:virtual void split()=0;virtual ~ISplitter(){}
};class BinarySplitter : public ISplitter{};class TxtSplitter: public ISplitter{};class PictureSplitter: public ISplitter{};class VideoSplitter: public ISplitter{};

3.2.2 MainForm1.cpp

class MainForm : public Form
{TextBox* txtFilePath;TextBox* txtFileNumber;ProgressBar* progressBar;public:void Button1_Click(){//面向接口编程的最基础表现形式,变量声明为抽象基类ISplitter * splitter=new BinarySplitter();//依赖具体类splitter->split();}
};

上述即为面向接口所编写的程序,之前介绍的模式背后都有一个抽象基类,这是面向对象设计模式的基础。

为什么要实现面向接口编程

设计原则-依赖倒置原则:依赖抽象,不依赖实现细节

		ISplitter * splitter=new BinarySplitter();//依赖具体类

ISplitter * splitter=是抽象依赖,而new BinarySplitter()是细节依赖,仍然存在细节依赖也是不可以的,简单来说MainForm 在编译时总体还是依赖BinarySplitter存在才能编译通过的,这就是编译时的细节依赖,这就违背了设计原则中的依赖倒置原则。这个问题如何解决呢?

再回过头看“对象创建模式”模式中提到的“通过“对象创建” 模式绕开new,来避免对象创建(new)”,为什么要避免对象创建(new)的原因也就是上面提到new BinarySplitter()带来的细节依赖。“它是接口抽象之后的第一步工作”可以理解为它是面向接口编程必然提出的需求,也就是面向接口编程不能只管ISplitter * splitter=的抽象依赖,而不管new BinarySplitter()的细节依赖,两边都要变为接口,变为依赖抽象。

想一想在C++中创建对象的方法

由于抽象类是不允许创建对象的,利用new和栈上创建对象的方法替换等号右边,实现右边为抽象的方法也是不可行的。

那么是否可以采用一种方法来返回一个对象,并且利用virtual实现运行时依赖,这就引出本篇的重点Factory Method工厂方法。

3.3 Factory Method工厂方法

3.3.1 ISplitterFactory.cpp

//抽象类
class ISplitter{
public:virtual void split()=0;virtual ~ISplitter(){}
};//工厂基类
class SplitterFactory{
public:virtual ISplitter* CreateSplitter()=0;virtual ~SplitterFactory(){}
};

3.3.2 MainForm2.cpp

class MainForm : public Form
{SplitterFactory*  factory;//工厂public:MainForm(SplitterFactory*  factory){this->factory=factory;}void Button1_Click(){//现在的返回值为ISplitter,但是真正创建的对象交给SplitterFactory未来,其中可以放具体的factory,即FileSplitter2.cpp具体工厂内容 ISplitter * splitter=factory->CreateSplitter(); //多态newsplitter->split();}
};

其中Button1_Click()是可以多次点击的,但SplitterFactory* factory;只需要一个即可(代码如上),MainForm中不需要具体指定具体的工厂,通常通过以下代码外接传递进来的一个具体的factory,例如BinarySplitterFactoryISplitter * splitter=factory->CreateSplitter();创建的就是一个BinarySplitter,这样就可以反复创建BinarySplitter,这个地方的形式我们称为多态new

    MainForm(SplitterFactory*  factory){this->factory=factory;}

此时可能会有人想,传进来的具体的factory在其他地方也要创建,也会对具体类产生依赖,这是对的,但是在MainForm没有对具体类,至于MainForm以外的是不归MainForm管的。

面向对象设计模式的松耦合设计很多情况下不是消灭变化(依赖具体类),而是将其赶到局部的地方。大家可以将变化比作一只猫,将其关到笼子里,而不是让它在你的代码里面跳来跳去

3.3.3 FileSplitter2.cpp

//具体类
class BinarySplitter : public ISplitter{};class TxtSplitter: public ISplitter{};class PictureSplitter: public ISplitter{};class VideoSplitter: public ISplitter{};//具体工厂
class BinarySplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new BinarySplitter();}
};class TxtSplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new TxtSplitter();}
};class PictureSplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new PictureSplitter();}
};class VideoSplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new VideoSplitter();}
};

上面代码就是一个完整的Factory Method工厂方法模式。

4. 模式定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。

​ ——《设计模式》GoF

结合代码来看,“定义一个用于创建对象的接口”指的 就是以下代码,具体即为:virtual ISplitter* CreateSplitter()=0;

//工厂基类
class SplitterFactory{
public:virtual ISplitter* CreateSplitter()=0;virtual ~SplitterFactory(){}
};

“让子类决定实例化哪一个类”即为以下代码:

//具体工厂
class BinarySplitterFactory: public SplitterFactory{
public:virtual ISplitter* CreateSplitter(){return new BinarySplitter();}
};
......

5. 结构

在这里插入图片描述

上图是《设计模式》GoF中定义的Factory Method工厂方法的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。

在这里插入图片描述

6. 要点总结

  • Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
  • Factory Method模式通过面向对象的手法(多态),将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
  • Factory Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。

第二条中的扩展也就是增加对应的factory即可。延迟而非更改最早的代码中,一旦需求发生变化,就需要在Mainform中更改ISplitter * splitter=new BinarySplitter();//依赖具体类,现在需求变化之后Mainform中ISplitter * splitter=factory->CreateSplitter(); 就不需要改变,只需要增加子类和子类工厂,传给Mainform即可。

7. 其他参考

C++设计模式——工厂方法模式

相关文章:

C++设计模式_08_Factory Method工厂方法模式

文章目录 1. “对象创建模式”模式1.1 典型模式 2. 动机(Motivation)3. 代码演示Factory Method工厂方法模式3.1 常规方法3.2 面向接口的编程3.2.1 FileSplitter1.cpp3.2.2 MainForm1.cpp 3.3 Factory Method工厂方法3.3.1 ISplitterFactory.cpp3.3.2 Ma…...

【TensorFlow1.X】系列学习笔记【基础一】

【TensorFlow1.X】系列学习笔记【基础一】 大量经典论文的算法均采用 TF 1.x 实现, 为了阅读方便, 同时加深对实现细节的理解, 需要 TF 1.x 的知识 文章目录 【TensorFlow1.X】系列学习笔记【基础一】前言线性回归非线性回归逻辑回归总结 前言 本篇博主将用最简洁的代码由浅入…...

Linux 基础操作手记三(内存篇)

Linux 基础操作手记三 释放内存虚拟机彻底无网络测试网速设置虚拟内存交换空间未使用虚拟机设置虚拟内存无法开机问题GParted - 分配内存系统盘扩容自己 释放内存 sync && echo 3 > /proc/sys/vm/drop_caches 虚拟机彻底无网络 还原默认设置,静静的等待…...

NodeJS的初使用,以及引入第三方插件和安装淘宝镜像的教程

NodeJs 命令 npm init -y 生成package.json文件npm i jquery --save–dev 开发依赖(jQuery后面还可以跟模块,可以有多个)npm i jquery --save 生产依赖npm i jquery --D 开发依赖npm uninstall jquery 卸载删除npm i 把删掉的模块,全部重新加载回来 1.介绍 1.什么是NodeJs?…...

Java读取文件的N种方法

1.概述 在这篇文章里, 我们将探索不同的方式从文件中读取数据。 首先, 学习通过标准的的Java类,从classpath、URL或者Jar中加载文件。 然后,学习通用BufferedReader, Scanner, StreamTokenizer, DataInputStream, SequenceInput…...

子类的构造与析构过程

一、简介 父类,也称基类,其构造方法和析构方法不能被继承; 子类,也称派生类,继承父类的方法和属性,但要加入新的构造和析构函数。 二、构造与析构过程 构造:先调用父类——>再调用子类 析构&…...

位运算相关笔记

位运算 Part 1:基础 左移:左移一位,相当于某数乘以 2 2 2。左移 x x x位,相当于该数乘以 2 x 2^x 2x。 右移:右移一位,相当于某数除以 2 2 2。右移 x x x位,相当于该数除以 2 x 2^x 2x。 与运算&…...

uniapp 安装 u-view 组件库

u-view 组件库安装教程:https://uviewui.com/components/install.html 注:以下使用 HBuilderx 安装 u-view 2.0 版本,不适用于其它版本。 1.安装 u-view 组件库 2、注册并登录 HBuilderx 账号,点击下载 u-view 组件库。 3、点击…...

Go 语言的成功案例:谁在使用 Go?

Go 语言,也被称为 Golang,是一门由Google开发的开源编程语言。自从2009年首次亮相以来,它在编程社区中崭露头角,并吸引了越来越多的开发者和组织。Go 以其高效的并发性、出色的性能和简单易懂的语法而闻名。在本文中,我…...

UG\NX二次开发 实时查看 NX 日志文件

文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 感谢粉丝订阅 感谢 a18037198459 订阅本专栏,非常感谢。 简介 实时查看 NX 日志文件,有助于分析保存时间等。打开WindowsPowerShell并实时获取日志文件内容的小功能。 效果 代…...

ZooKeeper+HBase分布式集群环境搭建

安装版本:hadoop-2.10.1、zookeeper-3.4.12、hbase-2.3.1 一、zookeeper集群搭建与配置 1.下载zookeeper安装包 2.解压移动zookeeper 3.修改配置文件(创建文件夹) 4.进入conf/ 5.修改zoo.cfg文件 6.进入/usr/local/zookeeper-3.4.12/zkdata…...

喜讯!持安科技入选2023年北京市知识产权试点单位!

近日,北京市知识产权局发布了“2023年度北京市知识产权试点示范单位及2020年度北京市知识产权试点示范单位复审通过名单”名单。 经过严格的初审、形式审核和专家评审,北京持安科技有限公司入选“2023年北京市知识产权试点单位”。 北京市知识产权试点示…...

笙默考试管理系统-MyExamTest----codemirror(39)

笙默考试管理系统-MyExamTest----codemirror(39) 目录 一、 笙默考试管理系统-MyExamTest 二、 笙默考试管理系统-MyExamTest 三、 笙默考试管理系统-MyExamTest 四、 笙默考试管理系统-MyExamTest 五、 笙默考试管理系统-MyExamTest 笙默考试…...

抛砖引玉:Redis 与 接口自动化测试框架的结合

接口自动化测试已成为保证软件质量和稳定性的重要手段。而Redis作为一个高性能的缓存数据库,具备快速读写、多种数据结构等特点,为接口自动化测试提供了强大的支持。勇哥这里粗略介绍如何结合Python操作Redis,并将其应用于接口自动化测试框架…...

网站如何才能不被黑,如何做好网络安全

当企业网站受到攻击时,首页文件可能被篡改,百度快照也可能被劫持并重定向到其他网站。首要任务是加强网站的安全防护。然而,许多企业缺乏建立完善的网站安全防护体系的知识。因此,需要专业的网站安全公司来提供相应的保护措施。今…...

人脸写真FaceChain风格写真的试玩(二)

接着上一篇【人脸写真FaceChain的简单部署记录(一)】来试玩一下。 1 无限风格写真 参考:让你拥有专属且万能的AI摄影师AI修图师——FaceChain迎来最大版本更新 1.1 人物形象训练 这里的步骤比较简单,就是选择照片,然…...

PHP 变量

变量 变量的声明、使用、释放 变量定义 形式 $ 变量名;严格区分大小写 $name; $Name; $NAME //三个变量不是同一个变量字母、数字、下划线组成,不能以数字开头,不能包含其他字符(空白字符、特殊字符) 驼峰式命名法、下划线式命名法 $first_name; $fi…...

牛客小白月赛79

给定一个数字n,你可以对它进行接下来的操作—— 选择数字中任意一个数位删除 例如对10​24选择操作百位,数字则变成了124;对1​024选择操作千位,数字则变成了024 我们称一个数字是干净的,当且仅当数字满足以下任意一种…...

面试算法31:最近最少使用缓存

题目 请设计实现一个最近最少使用(Least Recently Used,LRU)缓存,要求如下两个操作的时间复杂度都是O(1)。 get(key):如果缓存中存在键key,则返回它对应的值…...

如何处理前端SEO(搜索引擎优化)?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...