框架中的单例模式

上一节我们介绍了单例模式模板
本节来讨论下,在框架代码中,怎样设计单例模式


考虑这种场景:
框架的开发者写了一个类 Config用来管理整个程序运行周期中的配置文件,整个程序中应该只有一个配置文件类,所以站在框架开发者的角度,这个类就应该是全局单例;(这是一个典型的单例模式应用场景,单例模式还有一些其他的应用场景,比如:日志记录,线程池管理等)
框架开发者为这个Config类提供了很多通用函数,也有一部分虚函数(站在框架设计者的角度,我们只能尽可能的考虑更多场景,提供一系列的通用函数;但是并不知道这个Config类能否满足所有的情况下的需求,如果而以满足需求,那么直接使用Config类的对象作为全局对象即可;如果满足不了需求,那么就需要应用程序的开发者,主动继承这个类,重写虚函数,或者是添加一些自定义函数,然后将应用程序开发者的设计的类的对象作为全局对象),当然Config类还有一个和其他单例类一样的静态函数 static Config* getInstance();用来获取全局单例对象

站在框架开发者的角度,是提供了一些通用的功能,也支持对类Config的扩展(继承Config,重写虚函数)
在程序运行时(应用程序开发者),能通过框架设计者提供的 static Config* getInstance();来获取到全局单例对象,这个对象可能是Config的实例,也可能是Config的子类的实例

那么框架设计者的这个类Config应该如何设计呢?

  • 框架开发者考虑到应用程序在运行时,不一定是直接使Config类的对象,有可能还会使用Config的子类对象,所以可以将getInstance()设计为模板函数, 并需要在类Config中添加一个静态成员变量,保存类的指针,如果应用程序未对Conifg类做扩展,那么这个指针指向类Config的对象,如果应用程序做出扩展,那么这个指针指向类Config的子类对象:

//框架中的 Config 类
Config* Config::m_ins = nullptr;
class Config
{
public:template <typename T=Config>static T* getInstance() {if (m_ins != nullptr){return dynamic_cast<T*>(m_ins);}return nullptr;}static void setGlobalConfig(Config* ins){m_ins = ins;}int getValue() {}virtual void restoreValue() {}protected:Config() {}~Config() {}static Config* m_ins;
};//应用程序对Config类做扩展
class CustomConfig: public Config 
{// 特定于CustomConfig的函数和数据
public:CustomConfig() {}~CustomConfig() {}void getCustomValue() {}virtual void restoreValue() override {}
};//使用时std::unique_ptr<CustomConfig> cfg = std::make_unique<CustomConfig>();Config::setGlobalConfig(cfg.get());Config::getInstance()->getValue();		//使用Config 类提供的基础函数Config::getInstance<CustomConfig>()->getCustomValue(); // 使用 CustomConfig类中的特有函数

用过Qt的同学们可以回过头想想,Qt的QApplication是不是也是类似,这里的QCoreApplication 就对应着我们的Config类,并且Qt和我们一样,并不知道当前设计的类是否足够用户使用;这里的QApplication就对应着CustomConfig

我们可以在程序中使用 qApp 获取到全局的 QApplication对象,就对应着我们可以使用getInstance<>()模板函数获取到全局的Custom类对象,

这里的self 指针,就是我们上面的 m_ins 指针,只不过QCoreApplication中没有一个类似于setGlobalConfig()的函数为self赋值,self指针的赋值操作,是Qt框架内部完成的, 所以才导致qApp这个宏定义是在QApplicaiton中,而不是在QCoreApplication中,这么一比较下来,我们当前的设计,比Qt具有更高的扩展性

#define qApp (static_cast<QApplication *>(QCoreApplication::instance()))
static QCoreApplication *instance() { return self; }

进一步思考:

上述例子中,我们演示的是用程序开发者对Config类做了扩展,使用了自定义的类CustomConifg类对象作为全局单例

那如果我们设计的Config类就足够满足应用程序的使用场景,那么应用程序开发者就不需要对Config类做扩展,他希望直接使用Config类对象就够了,那么代码又该如何写呢?

    std::unique_ptr<Config> cfg = std::make_unique<Config>();	//主动创建Config::setGlobalConfig(cfg.get());	//设置全局单例对象Config::getInstance()->getValue();		//使用Config 类提供的基础函数

这样是否增加了应用程序使用者的负担了呢? 对于应用程序的开发者,他希望直接就能通过Config::getInstance()函数获取到全局的 Config类对象,而不是还需要他自己去创建对象,并且设置给全局,因为他并没有对Config类做任何扩展

回到框架设计者的角度,那么这个Config类又该如何设计呢? 可否在第一次使用Config::getInstance()函数时,如果类对象没有指定,那么就创建一个默认的对象?

	template <typename T=Config>static T* getInstance() {if (m_ins == nullptr)	//创建Config类对象,或者是子类对象{static std::mutex mu;std::lock_guard<std::mutex> lock(mu);if (m_ins == nullptr){m_ins = new T();}}if (m_ins != nullptr){return dynamic_cast<T*>(m_ins);}return nullptr;}//使用
Config::getInstance()->getValue();	//第一次调用时,会主动创建一个Config类对象作为全局单例
Config::getInstance<CustomConfig>()->getCustomValue(); //第一次调用时,会主动创建一个CustomConfig类对象作为全局单例

这样就免去了应用程序使用时,主动创建对象,并将对象设置为全局对象的过程;变成了在应用程序第一次使用 Config::getInstance<>()模板函数时,去创建对象,也就是通俗的懒汉式单例模式;


继续思考

思考:如果这里的Config构造函数需要参数,或者CustomConfig类构造函数需要参数,那么框架中的getInstance()函数又该如何设计?

回答:只需要将getInstance()模板函数设计成支持可变参数的模板即可,我们这里假设CutomConfig类需要两个int类型的参数:

    template<typename T = Config, typename ...Args>static T* getInstance(Args&&... args){if (m_ins == nullptr){std::lock_guard<std::mutex> lock(mu);if (m_ins == nullptr){m_ins = new T(std::forward<Args>(args)...);}}return static_cast<T*>(m_ins);}

使用:

	Config::getInstance<CustomConfig>(10, 20)->getCustomValue(); //使用两个int类型参数,构造全局CustomConfig类对象,并调用自定义函数

到这里,我们框架中的这个Config类是否已经足够完美了呢?


思考: 站在应用程序开发者的角度, 以上的方式,除了在首次创建CustomConfig类对象时,需要传递参数,并且应用程序开发者想要使用CustomConfig类对象时,都需要传递参数,并且后续传入的参数已经失去了作用,因为全局对象已经使用首次的参数创建好了,后续即使传递参数,也不会再创建新的对象,这样明显不合理了; 如果能做到仅仅在第一次创建对象时传递参数,后续使用不需要再传构造参数,那么还可以接受;

那么框架设计者应该如何更进一步设计这个 Config 类呢?
参考上一节,我们同样可以使用一个不带参数的 getInstance()函数作为重载:

    template<typename T = Config, typename ...Args>static T* getInstance(Args&&... args){if (m_ins == nullptr){std::lock_guard<std::mutex> lock(mu);if (m_ins == nullptr){m_ins = new T(std::forward<Args>(args)...);}}return static_cast<T*>(m_ins);}//重载版本,无需传递构造参数的 getInstance() 函数template<typename T = Config>static T* getInstance(){return static_cast<T*>(m_ins);}

使用:

 Config::getInstance<CustomConfig>(10, 20)->getCustomValue();	//首次使用参数创建全局类对象Config::getInstance<CustomConfig>()->getCustomValue();	//需后续无需参数,使用类对象

返璞归真

从头到尾,思考和改动了这么多次,读者你认为哪种方式最好呢?
我还是觉得,使用 setGlobalConfig()的方式最好,我们后面的思考和改进,都是在针对应用程序开发者会这样使用这个Config类而发起的;于是针对可能的使用场景,做出相应的改进。
我认为无论应用程序开发者觉得最开始的Config类是否足够使用,他都应该在应用程序代码中,主动的创建Config (或子类)对象,并调用setGlobalConfig函数,将其设置为全局单例

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

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

相关文章

SQL注入简单总结

一、SQL注入是什么 SQL注入即&#xff1a;是指web应用程序对用户输入数据的合法性没有判断或过滤不严&#xff0c;攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句&#xff0c;在管理员不知情的情况下实现非法操作&#xff0c;以此来实现欺骗数据库服…

武汉星起航:引领跨境电商新潮流,一站式孵化助力卖家轻松出海

武汉星起航电子商务有限公司&#xff0c;作为跨境电商领域的领军者&#xff0c;始终秉持“走出去”的战略理念&#xff0c;依托自营店铺的丰富经验和对跨境电商资源的深度整合&#xff0c;成功打造了一站式卖家孵化体系。这一体系集开店策划、运营教学、资源服务于一体&#xf…

Web安全知识

第二章 虚拟机运行架构&#xff1a; 1.寄居结构 2.原生架构 软件 注&#xff1a;Hyper-V是在Windows 2008操作系统上 附录 连接FTP服务器过程&#xff1a; 1.下载了软件&#xff1a; 2.连接到ftp://10.0.105.223/服务器&#xff08;访问老师课堂资源地址&#xff09; 关闭…

视频教程如何生成二维码?扫码看操作教程视频的制作技巧

产品使用教程的视频二维码如何制作呢&#xff1f;现在商家为了能够让用户可以快速的了解产品的使用说明或者安装教程&#xff0c;会选择将录制的相关视频生成二维码之后&#xff0c;打印到包装上或者通过客服人员发送给用户&#xff0c;用手机扫描对应二维码就可以获取教程&…

__UINT8_TYPE__ 和 __UINT16_TYPE__

__UINT8_TYPE__ 和 __UINT16_TYPE__ 是 C 中预定义的整数类型别名&#xff0c;它们通常由编译器提供&#xff0c;用于确保与特定平台上的原生无符号整数类型相匹配。这些类型别名通常出现在编译器的 <cstdint> 或 <stdint.h> 头文件中&#xff0c;用于提供标准、跨…

消息队列的简介

什么是消息队列? 消息队列就是用于不同系统 不同服务之间异步地传递信息,就是不用生产者和消费者同时在线或者直接连接,消息存储在队列中,直到消费者准备处理 消息队列的核心概念: 生产者:发送消息的一方 消费者:处理消息的一方 队列:存储队列的一方 优点: 1解耦: 生产者和消费…

C#发票检验真伪示例、全电票查验接口、发票OCR识别

假发票的最直接危害体现在税收的流失&#xff0c;但发票查验对于现如今票据量大的企业而言成为了一大难题&#xff0c;传统手动输入发票信息的方式已无法满足当下企业的需求。人工智能时代&#xff0c;“以票控账&#xff0c;以票审计&#xff0c;以票查税"是推动企业数字…

Spring Cloud OpenFeign底层实现原理

Spring Cloud OpenFeign底层实现原理 先说一下写这篇文章的一个原因&#xff0c;就是我被面试官吊打了&#xff0c;我只知道OpenFeign底层采用了RestTemplate进行调用&#xff0c;采用了动态代理&#xff0c;但是具体怎么实现的我就母鸡了。为了防止同样的地方摔倒&#xff0c…

【可靠数据传输的原理】

文章目录 可靠数据传输的原理可靠数据传输&#xff08;rdt&#xff09;的原理可靠数据传输&#xff1a;问题描述Rdt1.0&#xff1a; 在可靠信道上的可靠数据传输Rdt2.0&#xff1a;具有比特差错的信道rdt2.0&#xff1a;FSM描述rdt2.0&#xff1a;没有差错时的操作rdt2.0&#…

数图可视化品类空间管理系统入编《零售门店数字化赋能专项报告(2024年)》

数图可视化品类空间管理系统荣幸入编中国连锁经营协会发布的 《零售门店数字化赋能专项报告&#xff08;2024年&#xff09;》&#xff0c;报告以零售门店为切入点&#xff0c;通过引入“5P”的技术框架及梳理业内配套最佳实践方案&#xff0c;理出一套科学的、完整的零售门店数…

内存管理(C/C++)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

所有图片按照固定数量保存到新建文件夹

一、需求 有很多图片在不同的文件夹中&#xff0c;将所有图片汇集起来&#xff0c;按照固定的数量保存到新建的文件夹中 二、代码&#xff1a; import os import shutil import glob# 图片文件夹路径 file_path r"E:\tu_ret"# 指定目标根目录 dst_root_dir r&qu…

C语言转型之路:从C到C++的类与对象初探

欢迎来CILMY23的博客 本篇主题为 C语言转型之路&#xff1a;从C到C的类与对象初探 个人主页&#xff1a;CILMY23-CSDN博客 个人专栏系列&#xff1a; Python | C语言 | 数据结构与算法 | C 感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 写在前头…

蜂窝物联:蜂窝云平台全介绍

蜂窝云平台 PART 01 PC端展示平台 GIS地图 将地块嵌入到GIS地图展示&#xff0c;可以清晰展示各个地块所在地里位置&#xff0c;可以点击各个地块&#xff0c;在该界面可以清晰查看所选择地块的设备数据、监控画面、设备开关控制、基地介绍、基地图片等信息。 界面支持个化…

企业会议简单要求

会议原则&#xff1a; * 能不开会就不开会&#xff0c;根据开会目的与目标来确定 * 分清自己的角色&#xff0c;做好会前准备和会议引导会议过程&#xff1a; 1. 会前准备 会议召集人会前准备工作: * why&#xff1a;开会是否能解决问题&#xff0c;是否需要开会* 需要多人&…

【35分钟掌握金融风控策略8】策略评审

目录 策略自动化开发系统 策略评审 策略评审流程 档案管理 策略评审文档设计和撰写 策略自动化开发系统 当前&#xff0c;金融机构大多注重提升金融风控相关的科技实力&#xff0c;希望依托科技降低风控门槛&#xff0c;提高风控效率&#xff0c;降低风控成本。 单维度策…

快速掌握缓存技术:学习多个缓存供应商(ehcache,redis,memcached,jetcache,j2cache)

缓存技术 缓存模拟缓存Spring缓存技术第三方缓存技术Ehcache缓存供应Redis缓存memcached缓存&#xff08;国内&#xff09; jetcache缓存供应商jetcache的基本使用设置外部服务设置本地服务 jetcache方法缓存j2cache 缓存 什么是缓存 缓存是一种介于数据永久存储介质与数据应用…

Spring的循环依赖问题如何解决

1 构造器注入&#xff1a;通过构造器注入可以避免循环依赖的问题。当两个或多个Bean之间存在循环依赖时&#xff0c;将依赖通过构造函数参数传递&#xff0c;这样Spring容器在创建Bean实例时就可以通过构造函数解决循环依赖。 2 Setter注入&#xff1a;与构造器注入类似&#x…

c++中什么叫做窄式转换(narrowing conversions)以及窄式转换解决方案

c中什么叫做窄式转换(narrowing conversions)以及窄式转换解决方案 什么叫做窄式转换(narrowing conversions) 窄化转换&#xff08;narrowing conversion&#xff09;是指将一个值转换为另一种类型&#xff0c;但转换过程中丢失了信息或超出了目标类型的表示范围的情况。在C…

C++ | Leetcode C++题解之第38题外观数列

题目&#xff1a; 题解&#xff1a; class Solution { public:string countAndSay(int n) {string prev "1";for (int i 2; i < n; i) {string curr "";int start 0;int pos 0;while (pos < prev.size()) {while (pos < prev.size() &&…