C++设计模式(1)——单例模式

news2024/11/25 2:19:50

亦称:单件模式、Singleton
意图
单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
在这里插入图片描述

问题

单例模式同时解决了两个问题, 所以违反了单一职责原则:

1、保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。

它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。

注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。
在这里插入图片描述

客户端甚至可能没有意识到它们一直都在使用同一个对象。

2、为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。

和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。

还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

如今, 单例模式已经变得非常流行, 以至于人们会将只解决上文描述中任意一个问题的东西称为单例。

解决方案

所有单例的实现都包含以下两个相同的步骤:

1、将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。

2、新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

真实世界类比

政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, ​ “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。

单例模式结构

在这里插入图片描述

伪代码

在本例中, 数据库连接类即是一个单例。 该类不提供公有构造函数, 因此获取该对象的唯一方式是调用 获取实例方法。 该方法将缓存首次生成的对象, 并为所有后续调用返回该对象。

// 数据库类会对`getInstance(获取实例)`方法进行定义以让客户端在程序各处
// 都能访问相同的数据库连接实例。
class Database is
    // 保存单例实例的成员变量必须被声明为静态类型。
    private static field instance: Database

    // 单例的构造函数必须永远是私有类型,以防止使用`new`运算符直接调用构
    // 造方法。
    private constructor Database() is
        // 部分初始化代码(例如到数据库服务器的实际连接)。
        // ……

    // 用于控制对单例实例的访问权限的静态方法。
    public static method getInstance() is
        if (Database.instance == null) then
            acquireThreadLock() and then
                // 确保在该线程等待解锁时,其他线程没有初始化该实例。
                if (Database.instance == null) then
                    Database.instance = new Database()
        return Database.instance

    // 最后,任何单例都必须定义一些可在其实例上执行的业务逻辑。
    public method query(sql) is
        // 比如应用的所有数据库查询请求都需要通过该方法进行。因此,你可以
        // 在这里添加限流或缓冲逻辑。
        // ……

class Application is
    method main() is
        Database foo = Database.getInstance()
        foo.query("SELECT ……")
        // ……
        Database bar = Database.getInstance()
        bar.query("SELECT ……")
        // 变量 `bar``foo` 中将包含同一个对象。

单例模式适合应用场景

如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。

单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。

如果你需要更加严格地控制全局变量, 可以使用单例模式。

单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

请注意, 你可以随时调整限制并设定生成单例实例的数量, 只需修改 获取实例方法, 即 getInstance 中的代码即可实现。

实现方式

在类中添加一个私有静态成员变量用于保存单例实例。

声明一个公有静态构建方法用于获取单例实例。

在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。

将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。

检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。

单例模式优缺点

在这里插入图片描述

与其他模式的关系

外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。

如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。

1、只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
2、单例对象可以是可变的。 享元对象是不可变的。
抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。

C++ 单例模式讲解和代码示例

单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但却会破坏代码的模块化特性。

在某些其他上下文中, 你不能使用依赖于单例的类。 你也将必须使用单例类。 绝大多数情况下, 该限制会在创建单元测试时出现。
使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 C++ 代码中的使用频率正在逐步减少。
识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。

基础单例

实现一个粗糙的单例非常简单。 你仅需隐藏构造函数并实现一个静态的构建方法即可。

相同的类在多线程环境中会出错。 多线程可能会同时调用构建方法并获取多个单例类的实例。

/**
 * The Singleton class defines the `GetInstance` method that serves as an
 * alternative to constructor and lets clients access the same instance of this
 * class over and over.
 */
class Singleton
{

    /**
     * The Singleton's constructor should always be private to prevent direct
     * construction calls with the `new` operator.
     */

protected:
    Singleton(const std::string value): value_(value)
    {
    }

    static Singleton* singleton_;

    std::string value_;

public:

    /**
     * Singletons should not be cloneable.
     */
    Singleton(Singleton &other) = delete;
    /**
     * Singletons should not be assignable.
     */
    void operator=(const Singleton &) = delete;
    /**
     * This is the static method that controls the access to the singleton
     * instance. On the first run, it creates a singleton object and places it
     * into the static field. On subsequent runs, it returns the client existing
     * object stored in the static field.
     */

    static Singleton *GetInstance(const std::string& value);
    /**
     * Finally, any singleton should define some business logic, which can be
     * executed on its instance.
     */
    void SomeBusinessLogic()
    {
        // ...
    }

    std::string value() const{
        return value_;
    } 
};

Singleton* Singleton::singleton_= nullptr;;

/**
 * Static methods should be defined outside the class.
 */
Singleton *Singleton::GetInstance(const std::string& value)
{
    /**
     * This is a safer way to create an instance. instance = new Singleton is
     * dangeruous in case two instance threads wants to access at the same time
     */
    if(singleton_==nullptr){
        singleton_ = new Singleton(value);
    }
    return singleton_;
}

void ThreadFoo(){
    // Following code emulates slow initialization.
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    Singleton* singleton = Singleton::GetInstance("FOO");
    std::cout << singleton->value() << "\n";
}

void ThreadBar(){
    // Following code emulates slow initialization.
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    Singleton* singleton = Singleton::GetInstance("BAR");
    std::cout << singleton->value() << "\n";
}


int main()
{
    std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<
                "If you see different values, then 2 singletons were created (booo!!)\n\n" <<
                "RESULT:\n";   
    std::thread t1(ThreadFoo);
    std::thread t2(ThreadBar);
    t1.join();
    t2.join();

    return 0;
}

线程安全单例

为了解决这个问题, 你必须在创建首个单例对象时对线程进行同步。

/**
 * The Singleton class defines the `GetInstance` method that serves as an
 * alternative to constructor and lets clients access the same instance of this
 * class over and over.
 */
class Singleton
{

    /**
     * The Singleton's constructor/destructor should always be private to
     * prevent direct construction/desctruction calls with the `new`/`delete`
     * operator.
     */
private:
    static Singleton * pinstance_;
    static std::mutex mutex_;

protected:
    Singleton(const std::string value): value_(value)
    {
    }
    ~Singleton() {}
    std::string value_;

public:
    /**
     * Singletons should not be cloneable.
     */
    Singleton(Singleton &other) = delete;
    /**
     * Singletons should not be assignable.
     */
    void operator=(const Singleton &) = delete;
    /**
     * This is the static method that controls the access to the singleton
     * instance. On the first run, it creates a singleton object and places it
     * into the static field. On subsequent runs, it returns the client existing
     * object stored in the static field.
     */

    static Singleton *GetInstance(const std::string& value);
    /**
     * Finally, any singleton should define some business logic, which can be
     * executed on its instance.
     */
    void SomeBusinessLogic()
    {
        // ...
    }
    
    std::string value() const{
        return value_;
    } 
};

/**
 * Static methods should be defined outside the class.
 */

Singleton* Singleton::pinstance_{nullptr};
std::mutex Singleton::mutex_;

/**
 * The first time we call GetInstance we will lock the storage location
 *      and then we make sure again that the variable is null and then we
 *      set the value. RU:
 */
Singleton *Singleton::GetInstance(const std::string& value)
{
    std::lock_guard<std::mutex> lock(mutex_);
    if (pinstance_ == nullptr)
    {
        pinstance_ = new Singleton(value);
    }
    return pinstance_;
}

void ThreadFoo(){
    // Following code emulates slow initialization.
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    Singleton* singleton = Singleton::GetInstance("FOO");
    std::cout << singleton->value() << "\n";
}

void ThreadBar(){
    // Following code emulates slow initialization.
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    Singleton* singleton = Singleton::GetInstance("BAR");
    std::cout << singleton->value() << "\n";
}

int main()
{   
    std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<
                "If you see different values, then 2 singletons were created (booo!!)\n\n" <<
                "RESULT:\n";   
    std::thread t1(ThreadFoo);
    std::thread t2(ThreadBar);
    t1.join();
    t2.join();
    
    return 0;
}

来源:https://refactoringguru.cn/design-patterns/singleton
仅供学习,非商业用途,侵删

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

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

相关文章

CSS给元素添加边框(样式、颜色、宽度)

给元素添加边框 CSS边框属性允许你指定一个元素边框的样式和颜色, 和边框宽度。 可以使用 border 属性将边框样式,颜色,和宽度 一起设置。 如果不设置其中的某个值&#xff0c;也不会出问题&#xff0c;比如 border: solid #ff0000; 也是允许的。 使用border-style属性设置边…

ORA-39002: 操作无效 ORA-39070: 无法打开日志文件

今天在oracle12c上导数据&#xff0c;出现了错误。导库脚本久经考验&#xff0c;不应该有什么问题&#xff0c;但就是报错了。错误开头2句是&#xff1a; ORA-39002: 操作无效 ORA-39070: 无法打开日志文件 网上搜来的结果&#xff0c;是存放导出文件的路径不对&#xff0c;就…

常用API(String、ArrayList)

API&#xff08;应用程序接口&#xff09; Java写好的技术&#xff08;功能代码&#xff09;&#xff0c;可以直接调用String概述java.lang.String类代表字符串&#xff0c;String类定义的变量可以用于指向字符串对象&#xff0c;然后操作该字符串Java程序中的所有字符串文字&a…

JavaEE进阶第三课:Spring更简单的对象存储和取出(上)

上篇文章介绍了Spring的创建和使用&#xff0c;讲解3了Bean对象的基本存储和取出&#xff0c;这篇文章我们将会介绍Spring更简单的对象存储 目录1.Bean的存储1.0准备工作1.1五大类注解1.1.2为什么要有这么多注解1.2方法注解1.1.1方法注解需要搭配类注解一起使用1.2.2方法重载怎…

详细实例说明+典型案例实现 对迭代法进行全面分析 | C++

第四章 迭代法 目录 ●第四章 迭代法 ●前言 ●一、迭代法是什么&#xff1f; 1.简要介绍 2.代码示例&#xff08;简单理解&#xff09; 3.生活实例 ●二、迭代法的典型案例——开平方&帕斯卡三角形 1.开平方 2.帕斯卡三角形 ●总结 前言 简单的来…

游戏服务器如何维护

随着游戏的不断发展&#xff0c;游戏服务器的维护的重要性日益提升。对于玩家而言&#xff0c;他们需要得到更好的体验和更快的速度来享受这个娱乐项目。而对于运营者来说&#xff0c;则是确保安全运行、避免中断或者延迟的工作。本文就将介绍游戏服务器如何维护。如果你的游戏…

基于混沌系统和DNA算法的RGB图像加密(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 本文介绍了基于混沌系统和DNA编码的彩色数字图像加密、解密、抗噪声性能分析以及抗裁剪性能分析。 &#x1f4da;2 运行结果 &…

Linux cksum命令

Linux cksum命令用于检查文件的CRC是否正确。确保文件从一个系统传输到另一个系统的过程中不被损坏。CRC是一种排错检查方式&#xff0c;该校验法的标准由CCITT所指定&#xff0c;至少可检测到99.998%的已知错误。指定文件交由指令"cksum"进行校验后&#xff0c;该指…

Stream 管道流

文章目录前言Stream Api1、流的创建2、中间操作2.1、有状态① distinct② sorted③ limit④ skip⑤ concat2.2、无状态① filter② map③ flatMap④ peek⑤ mapToInt、mapToLong、mapToDouble、flatMapToDouble、flatMapToInt、flatMapToLong⑥ unordered3、终结操作3.1、短路操…

如何快速升级 Cocos Shader 版本,以简易水shader为例

白背景讲述如何 将一份 3.0.0 版本的水shader 升级至 Cocos Creator 3.6 。希望对大家有所帮助。环境Cocos Creator 3.6.2效果玉此处是鱼&#x1f41f;&#xff0c;介绍如何使用在资源管理器中新建着色器(Effect)复制 文末的 mywater.effect 代码至当前文件在资源管理器中新建材…

Ai绘画生成软件哪个好用?这款AI作画的二次元太精致了

Ai绘画生成软件哪个好用呢&#xff1f;今天小编给大家推荐一款AI作画神器&#xff0c;用它生成的二次元真的是超级惊艳&#xff0c;每天每个手机号可以免费生成多张画作。 我们打开数画ai绘画&#xff0c;这是一款国产软件&#xff0c;使用的是自身研发的算法&#xff0c;目前…

Vue3+TypeScript系统学习(十五) - 详解Vue3 Composition API(二)

前面给大家分享了Options API语法中代码的复用、Options API编码的优缺点&#xff0c;以及setup函数&#xff0c;响应式API等&#xff0c;这次将给大家分享Vue3 Composition API中的计算属性&#xff0c;侦听器&#xff0c;生命周期函数&#xff0c;Provide和Inject等。 1.1 co…

商户绑卡银行卡流程设计优化

一、背景 历史商户系统&#xff0c;断断续续经过好多人开发&#xff0c;商户绑卡流程数据好多地方不同步。 商户绑卡会调用支付平台进行绑卡&#xff0c;但是历史平台将数据留存了一份&#xff0c;所以目前现状&#xff0c;商户平台维护一份数据&#xff0c;支付平台维护一份数…

吴恩达机器学习课程笔记:模型描述、假设函数、代价函数

1.吴恩达机器学习课程笔记&#xff1a;模型描述、假设函数、代价函数 吴恩达机器学习课程笔记&#xff1a;模型描述 吴恩达机器学习课程笔记&#xff1a;代价函数 仅作为个人学习笔记&#xff0c;若各位大佬发现错误请指正 什么是模型&#xff1f; 机器学习模型简单来说&#…

玩转数据结构前言

本章为数据结构学习前的一些学习方法建议 51刷题法 leetcode中文站点: https://leetcode-cn.com leetcode国际站点: https://leetcode.com 刷题方法 Round1 1.读题&#xff0c;思考&#xff0c;4-6分钟内有思路立马写&#xff0c;15分钟内写不出来直接放弃 有思路先别管算…

使用mongostat命令实现zabbix监控mongodb

zabbix监控mongodbmongostat命令监控脚本参考zabbix配置mongostat命令 mongostat是mongodb自带的状态检测工具&#xff0c;可以使用这个命令获取mongodb的当前运行状态&#xff0c;并输出。我们使用这个命令获取mongodb的状态。 本示例中是一个3节点的复制集群&#xff08;一…

调用方系统禁止依赖传递后如何排查本系统所缺少依赖

订单系统依赖了好多其他系统&#xff0c;但当其他系统的service-api的包里依赖项加入了禁止依赖后&#xff0c;订单系统启动就会报错&#xff0c;具体排查那些包被禁止依赖了&#xff0c;方案如下 举例&#xff1a;会员系统后个包加入了禁止依赖<optional>true</opti…

Java中set的基本操作

文章目录1、定义Set2、增删改操作&#xff08;1&#xff09;新增&#xff08;2&#xff09;删出&#xff08;3&#xff09;修改3、访问set4、遍历set&#xff08;1&#xff09;foreach遍历&#xff08;2&#xff09;iterator迭代器遍历5、set转换6、其他操作set是一个无序的集合…

深度解析源码之SpringMVC文件上传为什么要用POST请求还要设置请求头

深度解析源码之SpringMVC文件上传为什么要用POST请求还要设置请求头 从本篇文章开始&#xff0c;来逐步介绍里面每一步的细节处理流程。 首先看到doDispatch方法的第一个重要操作就是校验文件上传请求。代码如下&#xff1a; 这个方法是如何校验文件上传请求的呢&#xff1f;…

软件测试最常用的 SQL 命令 | 通过实例掌握基本查询、条件查询、聚合查询

本文为霍格沃兹测试学院优秀学员学习笔记&#xff0c;汇总了软件测试人员经常使用&#xff0c;必须掌握的 SQL 基础命令1、DML核心CRUD增删改查缩写全称和对应 SQL&#xff1a;* DML 数据操纵语言&#xff1a;Data Manipulation Language* Create 增加&#xff1a;insert* Ret…