设计模式——对象创建模式之工厂模式

news2025/1/21 18:39:30

文章目录

  • 前言
  • 一、“单一职责” 模式
  • 二、Factory Method 工厂方法
    • 1、动机
    • 2、模式定义
    • 3、伪代码示例
    • 4、结构
  • 总结


前言


一、“单一职责” 模式

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

二、Factory Method 工厂方法

1、动机

  • 在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化
  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?

2、模式定义

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

注:"解耦"是解new和后面具体的类的耦合。

3、伪代码示例

原始代码:

class BinarySplitter 
{
public:
    void split() {
        //...
    }
};

MainForm.cpp

//MainForm.cpp
class MainForm : public Form
{
    TextBox* txtFilePath;
    TextBox* txtFileNumber;
    ProgressBar* progressBar;
 
public:
    void Button1_Click(){
        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());
        FileSplitter splitter(filePath, number, progressBar);
        splitter.split();
    }
};

未来可能的需求:文本分割,图片分割,那就要新增:

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

面向接口编程最简单的表现就是变量是接口类型
MainForm1.cpp

//MainForm1.cpp
class MainForm : public Form
{
    TextBox* txtFilePath;
    TextBox* txtFileNumber;
 
public:
    void Button1_Click(){
        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());
        //ISplitter* splitter 是抽象依赖
        //new BinarySplitter(filePath, number) 是细节依赖,编译时细节依赖, 违背了依赖倒置原则,如何解决呢?
        ISplitter* splitter = new BinarySplitter(filePath, number); //依赖具体类
        splitter.split();
    }
};

”对象创建“ 模式就是要绕开这个new 带来的问题,这是面向接口编程必然要面临的需求,上述代码中的第13行等号左右两边都变成依赖抽象。可以考虑通过一个方法返回对象。
SplitterFactory1.cpp

//SplitterFactory1.cpp
//抽象类
class ISplitter {
public:
    virtual void split() = 0;
    virtual ~ISplitter() { }
}
 
//工厂基类
class SplitterFactory {
public:
    ISplitter* createSplitter() {
        return new BinaryFiltter();
    }
};

MainForm2.cpp

//MainForm2.cpp
class MainForm : public Form
{
    TextBox* txtFilePath;
    TextBox* txtFileNumber;
 
public:
    void Button1_Click(){
        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());
        SplitterFactory factory;
        ISplitter* splitter = factory.createSplitter(); 
        splitter.split();
    }
};

因为 SplitterFactory 编译时依赖了 BinarySplitter,而 MainForm 编译时依赖了 SplitterFactory,所以相当于MainForm 编译时依赖了 BinarySplitter,所以还是没有解决问题。那应该怎么办呢?

虚函数是运行时依赖,所以修改 SplitterFactory.cpp:

SplitterFactory.cpp

//SplitterFactory.cpp
class SplitterFactory {
public:
    virtual ISplitter* createSplitter() = 0;
    virtual ~SplitterFactory() { }
};
 
class ISplitter {
public:
    virtual void split() = 0;
    virtual ~ISplitter() { }
}

MainForm3.cpp

//MainForm3.cpp
class MainForm : public Form
{
    TextBox* txtFilePath;
    TextBox* txtFileNumber;
 
public:
    void Button1_Click(){
        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());
        
        SplitterFactory* factory;
        ISplitter* splitter = factory->createSplitter();  //多态
        
        splitter.split();
    }
};

未来的对象实际是什么类型依赖于 factory,那么 factory 的实际类型是什么呢:
FileSplitter2.cpp

//FileSplitter2.cpp
//具体类
class BinarySplitter : public ISplitter
{
    virtual void split() {
        //...
    }
};
 
class TxtSplitter : public ISplitter
{
    virtual void split() {
        //...
    }
};
 
class PictureSplitter : public ISplitter
{
    virtual void split() {
        //...
    }
};
 
class VideoSplitter : public ISplitter
{
    virtual void split() {
        //...
    }
};
 
//具体工厂
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();
    }
};

MainForm3.cpp

//MainForm3.cpp
class MainForm : public Form
{
    SplitterFactory* factory; //工厂
public:
    MainForm(SplitterFactory* factory) {
        this->factory = factory;
    }
    
    void Button1_Click(){
        ISplitter* splitter = factory->createSplitter();  //多态new
        splitter.split();
    }
};

可以发现,通过这种改善,MainForm 只依赖 SplitterFactoryISplitter 这两个抽象,而没有依赖具体类。不是消灭变化,而是将变化赶到一个局部的地方。

4、结构

在这里插入图片描述

【注】:

  • Product -> ISplitter (稳定)
  • Creator -> SplitterFactory (稳定
  • ConcreteProduct -> XXSplitter (变化
  • ConcreteCreator -> XXSplitterFactory (变化

总结

  • Factory Method 模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
  • Factory Method 模式通过面向对象【注:多态】的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。【注:“延迟” 对应到代码中就是 MainForm 类中,一开始只要有需求变化,就要修改对应的代码,而改善后 MainForm 中不会因为需求的变化而进行更改,只需要加子类和子类的工厂即可,然后将具体的类传给 MainForm。】
  • Factory Method 模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。

我的qq:2442391036,欢迎交流!


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux系统之dstat命令的基本使用

Linux系统之dstat命令的基本使用 一、dstat命令介绍1. dstat简介2. dstat特点 二、本次实践介绍1. 本地环境规划2. 本次实践介绍 三、本地环境检查1. 检查操作系统版本2. 查看系统内核版本3. 检查本地yum仓库源状态 三、安装dstat工具1. 搜索dstat软件2. 安装dstat工具3. 查看d…

海康威视 2024届 数字逻辑设计 实习笔试分析

说明 记录一下 5月11日晚,做的海康威视的一场笔试。分享给需要的IC人。 岗位:数字逻辑设计工程师(浙江 杭州) 转载需要本人同意! 我的见解不一定都是准确的,欢迎评论区交流指正~~ 单选题 1、&#xff…

springboot+vue漫画之家系统(源码+文档)

风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的漫画之家系统。项目源码以及部署相关请联系风歌,文末附上联系信息 。 💕💕作者:风歌&a…

TweenMax介绍

GSAP 之 TweenMax 介绍(一) 一、背景 GreenSock (绿袜子) GreenSock 是一家做 专业级 JavaScript 动画的公司,主要产品就是其下的 GSAP (GreenSock Animation Platform),配合着 GSPA 开发了很多专业的动画…

做一个好玩的,给小猫拍照。web 端实现,发布图片,浏览图片。

0:先试试看 hongweizhu.com/#/cat 。 1:上班的路上会路过一家宠物店,里面有一只小猫,给它拍点照片,增加一点乐趣。 2: 使用到的技术 MongoDB 数据库(我暂时不想把图片直接放到服务器某个目录上,…

你对这4个ICT行业的网络设备,可能一无所知

晚上好,我是老杨。 上个月给你整了篇安全方向的报告分析,反响不错。 那篇主要是对网络安全的就业前景和怎么入门进行了具体分析,没看过的可以看看:《一不留神,网络安全工程师的岗位需求,破237万了》。 不…

混合精度是如何加速大模型训练的?

混合精度是如何加速大模型训练的? 基础知识回顾float-32从float-32 到float-16 混合精度计算bfloat16 基础知识回顾 float-32 在深度学习中,通常使用float-32 精度的数值训练模型,其中pytorch默认的也是float-32。 float32,也就…

每日一练 | 华为认证真题练习Day43

1、关于访间控制列表编号与类型的对应关系,下面描述正确的是()。 A. 基本的访问控制列表编号范围是1000-2999 B. 二层的访问控制列表编号范围是4000-4999 C. 高级的访间控制列表编号范围是3000-4000 D. 基于接口的访问控制列表编号范围是…

IO + File 详细基础知识

文章目录 IO File一、 File二、IO流2.0 IO流介绍2.1 字节流2.1.1 字节输出流 - FileOutputStream2.1.1.1 write方法2.1.1.2 字节输出流细节2.1.1.3代码实现2.1.1.4 换行与续写 2.1.2 字节输入流 - FileInputStream2.1.2.1 read()方法2.1.2.2 字节输入流细节2.1.2.3 代码实现 2…

PostgreSQL-分布式事务之两阶段提交

什么是ACID 在日常操作中,对于一组相关操作,通常需要其全部成功或全部失败。 在关系型数据库中,将这组相关操作称为“事务”。 在一个事务中,多个插入、修改、删除操作要么全部成功,要么全部失败,这称为…

SpringCloud Nacos 注册配置中心

前言 在微服务架构中,注册中心是核心的基础服务之一。相信不少同学都用过 Dubbo 这个流行分布式框架,很久之前微服务还没这么盛行,Dubbo就提供了比较完善的服务治理功能,而服务治理的实现主要依靠的就是注册中心。 许多同学接触…

Apache Kafka - 生产者内存优化注意事项

文章目录 1. 调优内存池参数2. 限制客户端生产速率3. 减小单条消息大小4. 监控生产者内存和性能5. 评估topic的partition分布6. 增加更多生产者实例7. Kafka升级和更强劲的硬件小结 1. 调优内存池参数 增大batchSize和linger ms,适当延长消息在内存池的最大延迟,减少发送次数。…

【C++初阶】第十二篇:priority_queue的使用与模拟实现

文章目录 priority_queue的使用priority_queue的介绍priority_queue的定义方式priority_queue各个接口的使用 仿函数代码样例使用场景(示例) priority_queue的模拟实现堆的向上调整算法堆的向下调整算法priority_queue的模拟实现 总结 priority_queue的使…

redis:基于 Streams 的消息队列

前言 Redis 5.0 及 5.0 以后的版本提供的Streams 是专门为消息队列设计的数据类型,它提供了丰富的消息队列操作命令。 消息队列 Streams 操作 XADD:插入消息,保证有序,可以自动生成全局唯一 ID; 名称为 mqstream 的…

【第七章:输入输出系统】

目录 知识框架No.0 引言No.1 输入输出系统一、基本概念二、I/O控制方式1、主机如何与I/O设备进行交互?2、CPU是如何通过I/O接口与外设交互的3、如何判断读入的数据有没有被输入完成呢?4、对于快速I/o设备,如“磁盘”,每准备好一个字就给CPu发…

【C语言】操作符详解(下)

操作符详解 1.条件操作符2. 逗号表达式3.下标引用,函数调用和结构体成员4.表达式求值4.1隐式类型转换4.2算术转换 5.操作符的属性 所属专栏:C语言 博主首页:初阳785 代码托管:chuyang785 感谢大家的支持,您的点赞和关注…

原生js手动实现一个多级树状菜单效果(高度可过渡变化) + 模拟el-menu组件实现(简单版)

文章目录 学习链接效果图代码要点 简单模拟el-menu实现TestTree.vueMenu.vueSubMenu.vue 学习链接 vue实现折叠展开收缩动画 - 自己的链接 elment-ui/plus不定高度容器收缩折叠动画组件 - 自己的链接 Vue transition 折叠类动画自动获取隐藏层高度以及手风琴效果实现 vue t…

Sqoop: Hadoop数据传输的利器【Sqoop实战】【上进小菜猪大数据系列】

我是上进小菜猪,沈工大软件工程专业,爱好敲代码,持续输出干货,欢迎关注。 Sqoop: Hadoop数据传输的利器, 在大数据领域,数据的传输和集成是至关重要的任务之一。Sqoop(SQL to Hadoop)作为Apache…

ChatGPT的前世今生,到如今AI领域的竞争格局,本文带你一路回看!

73年前,“机器思维”的概念第一次被计算机科学之父艾伦图灵(Alan Turing)提出,从此,通过图灵测试成为了人类在AI领域为之奋斗的里程碑目标。 73年后的今天,在AI历经了数十年的不断进化、迭代后&#xff0c…

【第二章:数据的表示和运算】

目录 知识框架No.0 引言No.1 数制与编码一、进位计数制及其相互转换二、BCD码三、无符号的整数在计算机内部表示和运算1、表示2、加法、减法实现 四、带符号的整数在计算机内部表示和运算1、表示1.1、原码表示1.2、原码形式实现加减法运算不行1.3 补码表示1.4 补码实现加法运算…