C++单例模式、工厂模式

一、单例模式

(一) 什么是单例模式
1. 是什么?

在系统的整个生命周期内,一个类只允许存在一个实例。

2. 为什么?

两个原因:

  • 节省资源。
  • 方便控制,在操作公共资源的场景时,避免了多个对象引起的复杂操作。
3. 线程安全

在单例模式的实现时,需要考虑线程安全问题。

什么是线程安全?

  • 多线程并行执行共享数据,线程安全的代码是可以通过同步机制来保证各个线程正常且正确地执行,不会产生数据的污染。

如何保证线程安全?

  • 加锁,保证资源每时每刻只被一个线程占用。
  • 让线程拥有资源,不用去共享。如:threadlocal可以为每一个线程维护一个私有的本地变量。
4. 单例模式分类
  • 懒汉式

系统运行中,实例并不存在,只有当需要使用时,才去创建。需要考虑线程安全。

  • 饿汉式(未雨绸缪)

系统一运行,就初始化创建实例,当需要时,直接调用。这样是线程安全的。

5. 单例类的特点
  • 构造函数和析构函数都是私有,防止外部构造和析构
  • 拷贝构造和赋值构造都是私有,防止外部拷贝和赋值
  • 一个静态的获取实例的方法,可以全局访问
(二) 单例模式实现
1. 加锁的懒汉式单例(线程安全)
#include <mutex>
#include <iostream>// 加锁的懒汉单例模式
class SingleInstance
{
public:// 获取实例static SingleInstance *getInstance();// 进程退出时,销毁实例static void deleteInstance();// 打印实例地址void printAddr() { std::cout << "实例的内存地址:" << this << "\n"; }private:// 私有的构造和析构SingleInstance();~SingleInstance();// 私有的拷贝构造和赋值构造,外部无法调用SingleInstance(const SingleInstance &singleInstance);const SingleInstance &operator=(const SingleInstance &singleInstance);// 对象指针和互斥锁static SingleInstance *singleInstance_;static std::mutex mutex_;
};
#include "sigleInstance.h"SingleInstance *singleInstance_ = nullptr;
std::mutex mutex_;// 获取实例
SingleInstance *SingleInstance::getInstance()
{   // 此处使用双检锁(两个if),只在指针为空的时候加锁,避免开销if(singleInstance_ == nullptr){std::unique_lock<std::mutex> lock(mutex_);if(singleInstance_ == nullptr){volatile auto tmp = new (std::nothrow) SingleInstance();singleInstance_ = tmp;}}return singleInstance_;
}// 进程退出时,销毁实例
void SingleInstance::deleteInstance()
{   std::unique_lock<std::mutex> lock(mutex_);  // 加锁if(singleInstance_){delete singleInstance_;singleInstance_ = nullptr;}
}
2. 经典实现方法:“静态局部变量”+“懒汉单例”(C++11线程安全)
class Single
{
public:static Single &getInstance(){// 局部静态变量只在第一次使用的时候初始化Single single;retunr single;}void print();private:Single();~Single();// 禁止拷贝和赋值Single(const Single &single) = delete;Single &operator=(const Single &single) = delete;
};
3. 饿汉式单例(线程安全)
#include <iostream>
// 饿汉模式
HungrySingle *hungrySingle_ = new (std::nothrow) HungrySingle();class HungrySingle
{
public:static HungrySingle *getInstance(){return hungrySingle_;}static deleteInstance(){if(hungrySingle_){delete hungrySingle_;hungrySingle_ = nullptr;}}void print() {}private:HungrySingle() {}~HungrySingle() {}HungrySingle(const HungrySingle& hungrySingle);const HungrySingle &operator= (const HungrySingle &hungrySingle);static HungrySingle *hungrySingle_;
};
4. 使用std::call_once实现单例(C++11线程安全)
#include <iostream>
#include <memory>
#include <mutex>class Singleton {
public:static std::shared_ptr<Singleton> getSingleton();void print() {}~Singleton() {}private:Singleton() {}
};static std::shared_ptr<Singleton> singleton = nullptr;
static std::once_flag singletonFlag;std::shared_ptr<Singleton> Singleton::getSingleton() {std::call_once(singletonFlag, [&] { singleton = std::shared_ptr<Singleton>(new Singleton()); });return singleton;
}

二、工厂模式

(一) 什么是工厂模式
1. 是什么、为什么?

工厂模式属于创建型模式,在创建对象的时候不会对客户端暴露创建逻辑,并且通过使用一个共同的接口来指向新创建的对像。将会利用到多态和继承。

2. 工厂模式分类
  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式
(二) 分析与实现
1. 简单工厂模式

哪个火爆就生产哪个(下面用鞋厂为例)。

核心组件:

  1. 工厂类:会定义一个用于创建指定的具体实例额接口。
  2. 抽象产品类:大类(如上图Shoes),用来继承。
  3. 具体产品类:工厂类创建的对象,具体的产品。

简单工厂模式的特点:

工厂类封装了创建具体产品对象的函数。

缺陷:

拓展性差,新增产品的时候,需要修改工厂。

代码实现:

// 鞋子抽象类
class Shoes
{
public:virtual ~Shoes() {}virtual void Show() = 0;
};class NiKeShoes : public Shoes
{
public:void Show() {}
};class AdidasShoes : public Shoes
{
public:void Show() {}
};class LiNingShoes : public Shoes
{
public:void Show() {}
};
enum SHOES_TYPE
{NIKE,LINING,ADIDAS
};// 总鞋厂
class ShoesFactory
{
public:// 根据鞋子类型创建对应的鞋子对象Shoes *CreateShoes(SHOES_TYPE type){switch (type){case NIKE:return new NiKeShoes();break;case LINING:return new LiNingShoes();break;case ADIDAS:return new AdidasShoes();break;default:return NULL;break;}}
};

在main函数调用时:

int main()
{// 构造工厂对象ShoesFactory shoesFactory;Shoes *pNikeShoes = shoesFactory.CreateShoes(NIKE);if (pNikeShoes != NULL){// 耐克球鞋广告pNikeShoes->Show();// 释放资源delete pNikeShoes;pNikeShoes = NULL;}Shoes *pLiNingShoes = shoesFactory.CreateShoes(LINING);if (pLiNingShoes != NULL){// 李宁球鞋广告pLiNingShoes->Show();// 释放资源delete pLiNingShoes;pLiNingShoes = NULL;}Shoes *pAdidasShoes = shoesFactory.CreateShoes(ADIDAS);if (pAdidasShoes != NULL){// 阿迪达斯球鞋广告pAdidasShoes->Show();// 释放资源delete pAdidasShoes;pAdidasShoes = NULL;}return 0;
}
2. 工厂方法模式

每种产品都有独立的生产线

核心组件:

  1. 抽象工厂类:用于继承的“大类”。
  2. 具体工厂类:继承于抽象工厂,实现创建对应具体产品对象的方法。
  3. 抽象产品类:用于继承的“大类”。
  4. 具体产品类:具体工厂创建的具体产品对象。

工厂方法模式的特点:

  • 抽象的工厂类(“大类”)。

工厂方法模式的缺陷:

  • 每多一个产品,就需要增加一个具体工厂(“生产线”),相比简单工厂模式,需要定义更多类。
  • 一个工厂只能生产一种产品。

工厂方法模式的代码:

// 总鞋厂
class ShoesFactory
{
public:virtual Shoes *CreateShoes() = 0;virtual ~ShoesFactory() {}
};// 耐克生产者/生产链
class NiKeProducer : public ShoesFactory
{
public:Shoes *CreateShoes() { return new NiKeShoes(); }
};// 阿迪达斯生产者/生产链
class AdidasProducer : public ShoesFactory
{
public:Shoes *CreateShoes() { return new AdidasShoes(); }
};// 李宁生产者/生产链
class LiNingProducer : public ShoesFactory
{
public:Shoes *CreateShoes() { return new LiNingShoes(); }
};

main函数执行:

int main()
{// 鞋厂开设耐克生产线ShoesFactory *niKeProducer = new NiKeProducer();// 耐克生产线产出球鞋Shoes *nikeShoes = niKeProducer->CreateShoes();// 耐克球鞋广告nikeShoes->Show();// 释放资源delete nikeShoes;delete niKeProducer;// 鞋厂开设阿迪达斯生产者ShoesFactory *adidasProducer = new AdidasProducer();// 阿迪达斯生产线产出球鞋Shoes *adidasShoes = adidasProducer->CreateShoes();// 阿迪达斯球鞋广告adidasShoes->Show();// 释放资源delete adidasShoes;delete adidasProducer;return 0;
}
3. 抽象工厂模式

产品不止一类,比如上面提到的鞋厂也要生产运动服。

核心组件:和工厂方法模式一样

抽象工厂模式的特点:

  • 一个接口,可以创建多个产品族中的产品对象。如上图,耐克工厂->耐克衣服、耐克鞋子。

抽象工厂模式的缺陷:和工厂方法模式一样,新增产品必须添加具体工厂类

抽象工厂模式代码:

// 基类 衣服
class Clothe
{
public:virtual void Show() = 0;virtual ~Clothe() {}
};// 耐克衣服
class NiKeClothe : public Clothe
{
public:void Show() {}
};// 基类 鞋子
class Shoes
{
public:virtual void Show() = 0;virtual ~Shoes() {}
};// 耐克鞋子
class NiKeShoes : public Shoes
{
public:void Show() {}
};
// 总厂
class Factory
{
public:virtual Shoes *CreateShoes() = 0;virtual Clothe *CreateClothe() = 0;virtual ~Factory() {}
};// 耐克生产者/生产链
class NiKeProducer : public Factory
{
public:Shoes *CreateShoes() { return new NiKeShoes(); }Clothe *CreateClothe() { return new NiKeClothe(); }
};

main函数执行:

int main()
{// 鞋厂开设耐克生产线Factory *niKeProducer = new NiKeProducer();// 耐克生产线产出球鞋Shoes *nikeShoes = niKeProducer->CreateShoes();// 耐克生产线产出衣服Clothe *nikeClothe = niKeProducer->CreateClothe();// 耐克球鞋广告nikeShoes->Show();// 耐克衣服广告nikeClothe->Show();// 释放资源delete nikeShoes;delete nikeClothe;delete niKeProducer;return 0;
}
(三) 封装性更好的工厂模式
1. 模板工厂

对于工厂方法模式,模板编程,把工厂类改为模板工厂类。

模板工厂模式代码:

产品类:

// 基类 鞋子
class Shoes
{
public:
virtual void Show() = 0;
virtual ~Shoes() {}
};// 耐克鞋子
class NiKeShoes : public Shoes
{
public:
void Show() {}
};// 基类 衣服
class Clothe
{
public:
virtual void Show() = 0;
virtual ~Clothe() {}
};// 优衣库衣服
class UniqloClothe : public Clothe
{
public:
void Show() {}
};

模板的工厂类:

// 抽象模板工厂类
// 模板参数:AbstractProduct_t 产品抽象类
template <class AbstractProduct_t>
class AbstractFactory
{
public:virtual AbstractProduct_t *CreateProduct() = 0;virtual ~AbstractFactory() {}
};// 具体模板工厂类
// 模板参数:AbstractProduct_t 产品抽象类,ConcreteProduct_t 产品具体类
template <class AbstractProduct_t, class ConcreteProduct_t>
class ConcreteFactory : public AbstractFactory<AbstractProduct_t>
{
public:AbstractProduct_t *CreateProduct(){return new ConcreteProduct_t();}
};
2. 产品注册模板类 + 单例工厂模板类

在模板工厂中,对工厂类进行了模板编程的封装。不过,对于产品类,还是缺少一个可以统一随时随地获取指定产品对对象的类。

对此,我们可以把产品注册的对象用map保存起来。

// 基类,产品注册模板接口类
// 模板参数 ProductType_t 表示的类是产品抽象类
template <class ProductType_t>
class IProductRegistrar
{
public:// 获取产品对象抽象接口virtual ProductType_t *CreateProduct() = 0;protected:// 禁止外部构造和虚构, 子类的"内部"的其他函数可以调用IProductRegistrar() {}virtual ~IProductRegistrar() {}private:// 禁止外部拷贝和赋值操作IProductRegistrar(const IProductRegistrar &);const IProductRegistrar &operator=(const IProductRegistrar &);
};// 工厂模板类,用于获取和注册产品对象
// 模板参数 ProductType_t 表示的类是产品抽象类
template <class ProductType_t>
class ProductFactory
{
public:// 获取工厂单例,工厂的实例是唯一的static ProductFactory<ProductType_t> &Instance(){static ProductFactory<ProductType_t> instance;return instance;}// 产品注册void RegisterProduct(IProductRegistrar<ProductType_t> *registrar, std::string name){m_ProductRegistry[name] = registrar;}// 根据名字name,获取对应具体的产品对象ProductType_t *GetProduct(std::string name){// 从map找到已经注册过的产品,并返回产品对象if (m_ProductRegistry.find(name) != m_ProductRegistry.end()){return m_ProductRegistry[name]->CreateProduct();}// 未注册的产品,则报错未找到std::cout << "No product found for " << name << std::endl;return NULL;}private:// 禁止外部构造和虚构ProductFactory() {}~ProductFactory() {}// 禁止外部拷贝和赋值操作ProductFactory(const ProductFactory &);const ProductFactory &operator=(const ProductFactory &);// 保存注册过的产品,key:产品名字 , value:产品类型std::map<std::string, IProductRegistrar<ProductType_t> *> m_ProductRegistry;
};// 产品注册模板类,用于创建具体产品和从工厂里注册产品
// 模板参数 ProductType_t 表示的类是产品抽象类(基类),ProductImpl_t 表示的类是具体产品(产品种类的子类)
template <class ProductType_t, class ProductImpl_t>
class ProductRegistrar : public IProductRegistrar<ProductType_t>
{
public:// 构造函数,用于注册产品到工厂,只能显示调用explicit ProductRegistrar(std::string name){// 通过工厂单例把产品注册到工厂ProductFactory<ProductType_t>::Instance().RegisterProduct(this, name);}// 创建具体产品对象指针ProductType_t *CreateProduct(){return new ProductImpl_t();}
};

关键之处:

  1. 在产品注册模板接口类中,产品的构造和析构是protected权限,用于在模板工厂注册产品。
  2. 模板工厂中,有一个map<产品名字,产品类型>,记录注册的产品。
  3. 单独一个模板产品注册类,它的构造函数用于向map注册产品,只能显式调用。

声明

工厂模式的内容是从小林coding相关文章总结而来,UML图也是来自小林coding。C++ 深入浅出工厂模式(进阶篇)_"我是耐克球鞋,我的广告语:just do it\" 工厂模式"-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_34827674/article/details/100864623

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

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

相关文章

Unity的相机跟随和第三人称视角二

Unity的相机跟随和第三人称视角二 展示介绍第二种相机模式远离代码总结 展示 我录的视频上可能看不太出来&#xff0c;但是其实今天的这个方法比原来写的那个方法更简便并且死角更少一些。 这里我也做了两个人物偏移的视角&#xff0c;按Q是原来的两个相机模式切换&#xff0c…

论文阅读笔记 | MetaIQA: Deep Meta-learning for No-Reference Image Quality Assessment

文章目录 文章题目发表年限期刊/会议名称论文简要动机主要思想或方法架构实验结果 文章链接&#xff1a;https://doi.org/10.48550/arXiv.2004.05508 文章题目 MetaIQA: Deep Meta-learning for No-Reference Image Quality Assessment 发表年限 2020 期刊/会议名称 Publi…

Marin说PCB之POC电路layout设计仿真案例---01

最近娃哈哈饮料突然爆火&#xff0c;看新闻后才知道春晚的的时候宗老已经病的很严重了&#xff0c;现在也已经离我们而去了&#xff0c;宗老是一个值得我们尊敬爱戴的伟大企业家。于是乎小编我立马去他们的直播间买了一箱娃哈哈AD钙奶支持一下我们的国货。 中午午休的时候&…

Excel如何开启VBA进行二次开发

经常使用Excel做数据分析的朋友平时用的比较多的可能只是一些常用的内置函数或功能&#xff0c;比如求和函数、字符串分割函数、分类汇总、IF函数、VLOOKUP函数等。大多数人认为Excel强大是因为内置了大量的函数。其实&#xff0c;作为一名资深程序猿&#xff0c;个人认为&…

kafka消费端消息去重方案

背景 我们在日常工作中&#xff0c;消费kafka消息是一个最常见的操作&#xff0c;不过由于kafka队列中经常包含重复的消息&#xff0c;并且消息量巨大&#xff0c;所以我们消费端总是需要先把消息进行去重后在消费&#xff0c;以减少消费端的压力&#xff0c;那么日常中我们一…

redis 性能优化一

目录 前言 尾延迟 前言 说到redis 性能优化&#xff0c;优化的目的是什么&#xff1f;提高响应&#xff0c;减少延迟。就要关注两点&#xff0c;一是尾延迟&#xff0c;二是Redis 的基线性能。只有指标&#xff0c;我们的优化&#xff0c;才有意义&#xff0c;才能做监控以及…

玩一会小乌龟

滚滚长江东逝水&#xff0c;浪花淘尽英雄。 是非成败转头空。青山依旧在&#xff0c;几度夕阳红。 白发渔樵江渚上&#xff0c;惯看秋月春风。 一壶浊酒喜相逢。古今多少事&#xff0c;都付笑谈中。 画一个正方形 import turtle# 创建一个Turtle对象 t turtle.Turtle()# 循环…

【开发工具】Git模拟多人开发场景理解分支管理和远程仓库操作

我们来模拟一个多人多分支的开发场景。假设你有一个新的空白远程仓库,假设地址是 https://github.com/user/repo.git。 克隆远程仓库到本地 $ git clone https://github.com/user/repo.git这会在本地创建一个 repo 目录,并自动设置远程主机为 origin。 创建本地开发分支并推送…

学术论文GPT的源码解读与二次开发:从ChatPaper到gpt_academic

前言 本文的前两个部分最早是属于此旧文的《学术论文GPT的源码解读与微调&#xff1a;从ChatPaper到七月论文审稿GPT第1版》&#xff0c;但为了每一篇文章各自的内容更好的呈现&#xff0c;于是我今天做了以下三个改动 原来属于mamba第五部分的「Mamba近似工作之线性Transfor…

开源模型应用落地-工具使用篇-Spring AI(七)

一、前言 在AI大模型百花齐放的时代&#xff0c;很多人都对新兴技术充满了热情&#xff0c;都想尝试一下。但是&#xff0c;实际上要入门AI技术的门槛非常高。除了需要高端设备&#xff0c;还需要面临复杂的部署和安装过程&#xff0c;这让很多人望而却步。不过&#xff0c;随着…

Kap - macOS 开源录屏工具

文章目录 关于 Kap 关于 Kap Kap 是一个使用web技术的开源的屏幕录制工具 官网&#xff1a;https://getkap.cogithub : https://github.com/wulkano/Kap 目前只支持 macOS 12 以上&#xff0c;支持 Intel 和 Apple silicon 你可以前往官网&#xff0c;右上方下载 你也可以使…

案例介绍:信息抽取技术在汽车销售与分销策略中的应用与实践

一、引言 在当今竞争激烈的汽车制造业中&#xff0c;成功的销售策略、市场营销和分销网络的构建是确保品牌立足市场的关键。作为一名经验丰富的项目经理&#xff0c;我曾领导一个专注于汽车销售和分销的项目&#xff0c;该项目深入挖掘市场数据&#xff0c;运用先进的信息抽取…

EasyExcel3.1.1版本上传文件忽略列头大小写

1、背景 项目中使用easyExcel3.1.1版本实现上传下载功能&#xff0c;相关数据DTO以 ExcelProperty(value "dealer_gssn_id") 形式规定其每一列的名称&#xff0c;这样的话easyExcel会完全匹配对应的列名&#xff0c;即用户上传文件时&#xff0c;列名写成Dealer_…

利用websocket +定时器简易的实现一个网络聊天室

其实原理非常简单,就是客户端用户通过websoket来连接websocket服务端。然后服务端,收集每个用户发出的消息, 进而将每条用户的消息通过广播的形式推送到每个连接到服务端的客户端。从而实现用户的实时聊天。 // TODO : 我主要是讲一下实现思路。并未完善其功能。 1.后端 依赖 …

使用数据库实现增删改查

#include<myhead.h>//定义添加数据函数int do_add(sqlite3 *ppDb) {//1.准备sql语句,输入要添加的信息int add_numb; //工号char add_name[20]; //姓名char add_sex[10]; //性别double add_score; //工资printf("请输入要添加的工号:")…

恢复IDEA误删除的git提交,提交被删除,尝试恢复提交

​​​​​​ dgqDESKTOP-JRQ5NMD MINGW64 /f/IdeaProjects/workspace/spzx-parent ((8bb112e...)) $ git reflog 8bb112e (HEAD, origin/master, master) HEAD{0}: checkout: moving from master to 8bb112e5ac18dfe4bbd64adfd06363e46b609f21 8bb112e (HEAD, origin/master, …

微信小程序开发系列(二十一)·wxml语法·setData()修改数组类型数据(增加、修改、删除)

目录 1. 新增数组元素 方法一&#xff1a;push&#xff08;&#xff09; 方法二&#xff1a;concat() 方法三&#xff1a;ES6中的扩展运算符 ... 2. 修改数组元素 样式一&#xff1a;数字 样式二&#xff1a;元素 3. 删除数组元素 方法一&#xff1a;splice&#x…

vue2源码分析-vue入口文件global-api分析

文章背景 vue项目开发过程中,首先会有一个初始化的流程,以及我们会使用到很多全局的api,如 this.$set this.$delete this.$nextTick,以及初始化方法extend,initUse, initMixin , initExtend, initAssetRegisters 等等那它们是怎么实现,让我们一起来探究下吧 源码目录 global-…

Windows下 OracleXE_21 数据库的下载与安装

Oracle 数据库的下载与安装 数据库安装包下载数据库安装访问数据库进行测试Navicat连接数据库 1. 数据库安装包的下载 1.1 下载地址 Oracle Database Express Edition | Oracle 中国 1.2 点击“下载 Oracle Database XE”按钮&#xff0c;进去到下载页面&#xff08;选择对…

Stable diffusion零基础课程

该课程专为零基础学习者设计&#xff0c;旨在介绍和解释稳定扩散的基本概念。学员将通过简单易懂的方式了解扩散现象、数学模型及其应用&#xff0c;为日后更深入的科学研究和工程应用打下坚实基础。 课程大小&#xff1a;3.8G 课程下载&#xff1a;https://download.csdn.ne…