框架中的单例模式

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


考虑这种场景:
框架的开发者写了一个类 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的qApp是不是也是类似,源码如下;这里的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类是否足够使用,他都应该在应用程序代码中,主动的创建Config (或子类)对象,并调用setGlobalConfig函数,将其设置为全局单例。

结尾

非常感谢你能认真看完这篇文章,如果它对你有所帮助,我将倍感荣幸。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vLLM-prefix浅析(System Prompt,大模型推理加速)

原文&#xff1a;vLLM-prefix浅析&#xff08;System Prompt&#xff0c;大模型推理加速&#xff09; 简介 本文浅析了在大模型推理加速方面一个非常优秀的项目 vLLM 的一个新特性 Prefix。在 Prompt 中有相同前缀时可以提高吞吐量降低延迟&#xff0c;换句话说可以省去这部分…

【做算法学数据结构】二叉树的层序遍历【二叉树】

文章目录 题目二叉树二叉树描述二叉树的java描述和前序遍历、后序遍历、中序遍历 题解总结以及二叉树应用场景 题目 给你二叉树的根节点 root &#xff0c;返回其节点值 自底向上的层序遍历 。 &#xff08;即按从叶子节点所在层到根节点所在的层&#xff0c;逐层从左向右遍历…

德思特GNSS模拟器为物流行业保驾护航

作者介绍 一、前言 德思特GNSS模拟器能够在最短的时间内高效、准确的协助完成虹科MSR运输数据记录仪的定位准确性以及抗干扰能力测试&#xff0c;确保在运输或存储过程中&#xff0c;让用户知道何时何地发生了超出预设公差范围的事件&#xff0c;快速、准确的记录定位数据&…

【UE 材质】水波纹效果

效果 模拟雨水打落在水面上的效果 步骤 1. 下载所需纹理和纹理 纹理2. 新建一个材质&#xff0c;这里命名为“M_WaterRipples” 打开“M_WaterRipples”&#xff0c;添加一个纹理采样节点&#xff0c;纹理使用第一步下载的纹理 将纹理采样节点的R通道连接到基础颜色&#x…

李沐57_长短期记忆网络LSTM——自学笔记

LSTM 1.忘记门&#xff1a;将值朝着0减少 2.输入门&#xff1a;决定不是忽略掉输入数据 3.输出门&#xff1a;决定是不是使用隐状态 !pip install --upgrade d2l0.17.5 #d2l需要更新首先加载时光机器数据集。 import torch from torch import nn from d2l import torch a…

Ajax和axios基础

AJAX Asynchronous JavaScript And XML 异步的JavaScript和XML 作用 数据交换: 通过Ajax可以给服务器发送请求,服务器将数据直接响应回给浏览器. 异步交互: 可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术. 同步和异步 同步发送请求: 浏览器发…

阿斯达年代记账号注册教程 阿斯达年代记苹果id注册教程

阿斯达年代记账号注册教程 阿斯达年代记苹果id注册教程 即将开服的新款大型多人角色扮演类游戏阿斯达年代记三强争霸将于4月24号上线&#xff0c;小伙伴们可以在本次开服之后进行游戏&#xff0c;这款游戏除了常规的职业分化之外&#xff0c;目前开放了四种角色供玩家选择&…

getopt, getopt_long使用笔记

An element of argv that starts with - (and is not exactly "-" or "--") is an option element. The characters of this element (aside from the initial -) are option characters. 以-’开头的字符(注意!不是字符串!!)就是命令行参数选项 通…

C++中的程序流程结构

一、选择结构 1.1 if语句 作用&#xff1a;执行满足条件的语句 if语句的三种形式 单行格式if语句多行格式if语句多条件的if语句 #include <iostream> using namespace std;int main(){//选择结构 单行if语句//用户输入分数&#xff0c;如果分数>600,视为考上一本大…

代码随想录 Day19 字符串 | LC28 实现strStr() 【KMP经典题目】

六、实现strStr() 题目&#xff1a; 力扣28&#xff1a;找出字符串中第一个匹配的下标 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack…

全身照怎么缩小做头像?在线图片改大小尺寸的方法

在日常工作中&#xff0c;有不少人喜欢把自己的全身照作为微信或者QQ头像&#xff0c;这时候就经常遇到一个问题&#xff0c;就是图片尺寸太大&#xff0c;无法完整的展现&#xff0c;那么这个时候该怎么处理呢&#xff1f;可以试试下面介绍的这个在线图片改大小尺寸的方法&…

上位机图像处理和嵌入式模块部署(树莓派4b的一种固件部署方法)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 如果软件开发好了之后&#xff0c;下面就是实施和部署。对于树莓派4b来说&#xff0c;部署其实就是烧录卡和拷贝文件。之前我们烧录卡&#xff0c;…

YashanDB连获多项权威认证

近期&#xff0c;YashanDB产品能力再获认可&#xff0c;顺利通过多项权威测试认证&#xff0c;包括通过《数据库政府采购需求标准(2023年版)》测评&#xff1b;通过国密检测机构测试&#xff0c;产品支持GB/T38636-2020《信息安全技术传输层密码协议(TLCP)》国标协议&#xff1…

BRC铭文NFT铸造质押挖矿系统开发运营

区块链技术的不断演进与应用拓展&#xff0c;为数字资产领域带来了更多可能性。BRC铭文NFT铸造质押挖矿系统的开发与运营&#xff0c;将为用户提供一种全新的数字资产体验&#xff0c;下文将介绍其版/需求方案/逻辑项目。 1. 系统概述 BRC铭文NFT铸造质押挖矿系统旨在结合区块…

『docker』 容器虚拟化技术之空间隔离实战

文章目录 容器虚拟化基础之 NameSpaceNameSpace 隔离实战实战目的基础知识dd 命令详解mkfs 命令详解df 命令详解mount 命令详解unshare 命令详解 实战操作一&#xff08;PID 隔离&#xff09;实战操作二&#xff08;Mount 隔离&#xff09; 容器虚拟化基础之 NameSpace 什么是…

RepViT:当MobileNet遇到ViT

paper&#xff1a;https://arxiv.org/abs/2307.09283 code&#xff1a;https://github.com/THU-MIG/RepViT 目录 0. 摘要 1. 引言 2. 相关工作 3. 方法 3.1. 准备工作 3.2. block设计 3.3. 宏观设计 3.4. 微观设计 3.5. 网络结构 4. 实验 4.1. Image Classification …

Day:动态规划 LeedCode 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV

123. 买卖股票的最佳时机 III 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意&#xff1a;你不能同时参与多笔交易&#xff08;你必须在再次购买前出售掉之前的股票&a…

系统架构设计精华知识

数据流风格&#xff1a;适合于分阶段做数据处理&#xff0c;交互性差&#xff0c;包括&#xff1a;批处理序列、管理过滤器。调用/返回风格&#xff1a;一般系统都要用到&#xff0c;包括&#xff1a;主程序/子程序&#xff0c;面向对象&#xff0c;层次结构&#xff08;分层越…

Rootkit介绍

一、定义 Rootkit是一种恶意软件&#xff0c;旨在让黑客访问和控制目标设备。虽然大多数Rootkit 会影响软件和操作系统&#xff0c;但有些还会感染计算机的硬件和固件。Rootkit善于隐藏自己&#xff0c;担当它们保持隐藏时&#xff0c;其实处于活跃状态。 一旦未经授权获得对计…

让更多的人能使用AI才能提升国内AI竞争力

随着人工智能技术的快速发展,AI正在深入影响我们的生活和工作。然而,目前AI技术的使用和应用主要集中在少数大型科技公司和研究机构,普通大众对AI技术的接触和使用还相对有限。如何让更多的人能够便捷地使用AI,从而带动整个国内AI产业的发展,已成为当前亟需解决的问题。 首先…
最新文章