单例模式(Singleton)

news2024/12/23 15:34:47

定义

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

前言

1. 问题

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

  1. 保证一个类只有一个实例
  2. 为该实例提供一个全局访问节点

为什么会有人想要控制一个类所拥有的实例数量?最常见的原因是控制某些共享资源(例如数据库或文件)的访问权限。它的运作方式是这样的:如果你创建了一个对象,同时过一会儿后你决定再创建一个新对象,此时你会获得之前已创建的对象,而不是一个新对象。

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

还记得你用过的那些存储重要对象的全局变量吗?它们在使用上十分方便,但同时也非常不安全,因为任何代码都有可能覆盖掉那些变量的内容,从而引发程序崩溃。和全局变量一样,单例模式也允许在程序的任何地方访问特定对象。但是它可以保护该实例不被其他代码覆盖。

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

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

结构

 

单例(Singleton) 类声明了一个名为getInstance 获取实例的静态方法来返回其所属类的一个相同实例。

单例的构造函数必须对客户端(Client) 代码隐藏。调用获取实例方法必须是获取单例对象的唯一方式。

适用场景

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

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

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

单例模式与全局变量不同,它保证类只存在一个实例。除了单例类自己以外,无法通过任何方式替换缓存的实例。请注意, 你可以随时调整限制并设定生成单例实例的数量,只需修改获取实例方法, 即getInstance 中的代码即可实现。

实现方式

  1. 在类中添加一个私有静态成员变量用于保存单例实例
  2. 声明一个公有静态构建方法用于获取单例实例
  3. 在静态方法中实现"延迟初始化"。该方法会在首次被调用时创建一个新对象,并将其存储在静态成员变量中。此后该方法每次被调用时都返回该实例。
  4. 将类的构造函数设为私有。类的静态方法仍能调用构造函数,但是其他对象不能调用。
  5. 检查客户端代码,将对单例的构造函数的调用替换为对其静态构建方法的调用。

优点

  • 你可以保证一个类只有一个实例。
  • 你获得了一个指向该实例的全局访问节点。
  • 仅在首次请求单例对象时对其进行初始化。

缺点

  • 违反了单一职责原则。该模式同时解决了两个问题。
  • 单例模式可能掩盖不良设计,比如程序各组件之间相互了解过多等。
  • 该模式在多线程环境下需要进行特殊处理,避免多个线程多次创建单例对象。
  • 单例的客户端代码单元测试可能会比较困难,因为许多测试框架以基于继承的方式创建模拟对象。由于单例类的构造函数是私有的,而且绝大部分语言无法重写静态方法,所以你需要想出仔细考虑模拟单例的方法。要么干脆不编写测试代码,或者不使用单例模式。

懒汉单例模式代码

1. 线程不安全的懒汉单例模式

Singleton.h:

  • 构造函数私有:即单例模式只能在内部私有化
  • 实例对象static:保证全局只有一个
  • 外界通过GetInstance()获取实例对象
#ifndef SINGLETON_H_
#define SINGLETON_H_

#include <iostream>
#include <string>

class Singleton {
 public:
    static Singleton* GetInstance() {
        if (instance_ == nullptr) {
            instance_ = new Singleton();
        }
        return instance_;
    }
 private:
    Singleton() {}
    static Singleton* instance_;
};

#endif  // SINGLETON_H_

Singleton.cpp: 

#include "Singleton.h"

// 静态变量instance初始化不要放在头文件中, 如果多个文件包含singleton.h会出现重复定义问题
Singleton* Singleton::instance_ = nullptr;

 main.cpp:

#include <iostream>
#include "Singleton.h"

int main() {
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();

    std::cout << "s1地址: " << s1 << std::endl;
    std::cout << "s2地址: " << s2 << std::endl;
    return 0;
}

线程安全

上述代码并不是线程安全的,当多个线程同时调用Singleton::GetInstance(),可能会创建多个实例从而导致内存泄漏(会new多次但我们只能管理唯一的一个instance_),我们这里简单通过互斥锁实现线程安全。

Singleton.hpp

#ifndef SINGLETON_H_
#define SINGLETON_H_

#include <iostream>
#include <string>
#include <mutex>

class Singleton {
 public:
    static Singleton* GetInstance() {
        if (instance_ == nullptr) {
            // 加锁保证多个线程并发调用getInstance()时只会创建一个实例
            m_mutex_.lock();
            if (instance_ == nullptr) {
                instance_ = new Singleton();
            }
            m_mutex_.unlock();
        }
        return instance_;
    }
 private:
    Singleton() {}
    static Singleton* instance_;
    static std::mutex m_mutex_;
};

#endif  // SINGLETON_H_

 Singleton.cpp:

#include "Singleton.h"

// 静态变量instance初始化不要放在头文件中, 如果多个文件包含singleton.h会出现重复定义问题
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::m_mutex_;

main.cpp: 

#include <iostream>
#include "Singleton.h"

int main() {
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();

    std::cout << "s1地址: " << s1 << std::endl;
    std::cout << "s2地址: " << s2 << std::endl;
    return 0;
}

饿汉单例模式代码

Singleton.h:

#ifndef SINGLETON_H_
#define SINGLETON_H_

class Singleton {
 public:
    static Singleton* GetInstance() {
        return instance_;
    }

 private:
    Singleton() {}
    static Singleton* instance_;
};

#endif  // SINGLETON_H_

Singleton.cpp:

#include "Singleton.h"

Singleton* Singleton::instance_ = new Singleton();

main.cpp: 

#include <iostream>
#include "Singleton.h"

int main() {
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();

    std::cout << "s1地址: " << s1 << std::endl;
    std::cout << "s2地址: " << s2 << std::endl;
    return 0;
}

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

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

相关文章

react签字功能 react-signature-canvas

react签字功能 react-signature-canvas . 前几天一个月薪35k的兄弟&#xff0c;给我推了一个人工智能学习网站&#xff0c;看了一段时间挺有意思的。包括语音识别、机器翻译等从基础到实战都有&#xff0c;很详细&#xff0c;分享给大家。大家及时保存&#xff0c;说不定啥时…

【MOOC 作业】第4章 网络层

不是标答也不是参考答案 仅从个人理解出发去做题 1、(20分) 考虑如图示的网络。 a. 假定网络是一个数据报网络。显示路由器 A 中的转发表&#xff0c;其中所有指向主机 H3 的流量通过接口 3 转发。 目的网络链路接口H33 b. 假定网络是一个数据报网络。你能写出路由器 A 中的…

K8S系列文章之 部署MySQL数据库

1 编写 mysql.yaml文件 apiVersion: v1 kind: Namespace metadata:name: devops # Namespace 的名称 --- apiVersion: apps/v1 kind: Deployment metadata:name: devops-mysql # deployment控制器名称namespace: devops spec:replicas: 1revisionHistoryLimit: 5strategy:…

Maven及IDEA配置

1.Maven的安装及环境变量配置 1. 下载压缩包&#xff0c;解压到指定位置&#xff1b; 2. 在系统环境变量中配置 maven 的 bin 路径&#xff1b; 3. 配置一下 maven 的本地仓库位置和阿里云镜像&#xff08;推荐大家下载 notepad 进行修改配置&#xff09;&#xff1b; 在 …

Mysql高阶语句与MySQL存储过程

Mysql高阶语句 准备环境&#xff08;1&#xff09; 一、MySQL高阶进阶SQL语句1、select2、distinct3、where4、and or5、in6、between7、通配符8、order by9、函数数学函数字符串函数 10、group by11、having12、别名13、子查询13、EXISTS 二、MySQL高阶进阶SQL语句2环境准备&a…

DINDIEN

DIEN模型 DIN存在的问题&#xff1a; DIN引入了attention机制来通过用户历史行为数据对用户兴趣进行建模&#xff0c;而缺乏对具体行为背后的序列信息或者说依赖关系进行专门的建模&#xff0c;也就是没法捕捉到用户的兴趣变化过程。 DIEN的改动&#xff1a; 这个模型既然是…

为什么黑客不黑/攻击赌博网站?

攻击了&#xff0c;只是你不知道而已&#xff01; 同样&#xff0c;对方也不会通知你&#xff0c;告诉你他黑了赌博网站。 攻击赌博网站的不一定是正义的黑客&#xff0c;也可能是因赌博输钱而误入歧途的法外狂徒。之前看过一个警方破获的真实案件&#xff1a;28岁小伙因赌博…

Vue3 组合式 API

前言 传统的组件随着业务复杂度越来越高&#xff0c;代码量会不断的加大&#xff0c;整个代码逻辑都不易阅读和理解。Vue3 使用组合式 API 的地方为 setup。在 setup 中&#xff0c;我们可以按逻辑关注点对部分代码进行分组&#xff0c;然后提取逻辑片段并与其他组件共享代码。…

【FFmpeg实战】编解码 AVCodec

转载自&#xff1a;https://www.cnblogs.com/wangyaoguo/p/8192273.html FFmpeg编解码 FFmpeg支持绝大多数视频编解码格式&#xff0c;如何遍历FFmpeg编解码器&#xff1f; 编解码器以链表形式存储&#xff0c;使用av_codec_next() 函数可以获取编解码器指针&#xff0c;当参数…

【YOLO】yolov5训练自己的数据集

文章目录 0 前期教程1 前言2 准备数据集2.1 数据集来源2.2 数据集结构介绍2.3 标签格式的转换 3 训练以及训练结果3.1 训练3.2 测试 4 数据标注5 后续教程 0 前期教程 【Python】朴实无华的yolov5环境配置 1 前言 上面前期教程中&#xff0c;大致介绍了yolov5开发环境的配置方…

Windows 10 安装 Redis

安装 Redis 1&#xff1a;下载 下载 Windows 版本的 Redis&#xff0c;点击这里 下载redis 2&#xff1a;解压 解压下载的 zip 包到任意目录&#xff0c;如我的目录&#xff1a; 3&#xff1a;启动 命令行进入刚才解压文件的根目录下&#xff0c;然后执行如下命令即可&a…

跌倒检测 关节点角度数学计算

参考&#xff1a; https://github.com/GitGudwl/MediapipePoseEstimationForFallDetection/tree/main https://blog.csdn.net/weixin_45824067/article/details/130646962 1、mediapipe 根据关节点角度计算 1、11与12取中间点&#xff0c;记为center_up; 23 与24取中间点记为c…

为什么自学Python会从入门到放弃?

前言 Python现在非常火&#xff0c;语法简单而且功能强大&#xff0c;很多同学都想学Python&#xff01;所以蛋糕给各位看官们准备了高价值Python学习视频教程及相关电子版书籍&#xff0c;欢迎前来领取&#xff01; 下面小编与大家分享一下自学Python的人&#xff0c;放弃的…

【unity造轮子】Unity ShaderGraph使用教程与各种特效案例(持续更新)

文章目录 一、前言二、ShaderGraph1.什么是ShaderGraph2.在使用ShaderGraph时需要注意以下几点&#xff1a;3.优势4.项目 三、实例效果外发光进阶&#xff1a;带方向的菲涅尔边缘光效果裁剪进阶 带边缘色的裁剪溶解进阶 带边缘色溶解卡通阴影水波纹积雪效果不锈钢效果UV抖动水波…

使用编码工具

本文主要介绍了对句子编码的过程&#xff0c;以及如何使用PyTorch中自带的编码工具&#xff0c;包括基本编码encode()、增强编码encode_plus()和批量编码batch_encode_plus()。 一.对一个句子编码例子 假设想在要对句子’the quick brown fox jumps over a lazy dog’进行编码…

【K8S系列】深入解析K8S存储

序言 做一件事并不难&#xff0c;难的是在于坚持。坚持一下也不难&#xff0c;难的是坚持到底。 文章标记颜色说明&#xff1a; 黄色&#xff1a;重要标题红色&#xff1a;用来标记结论绿色&#xff1a;用来标记一级论点蓝色&#xff1a;用来标记二级论点 Kubernetes (k8s) 是一…

ppp协议,一文带你了解

一、PPP协议简介 PPP&#xff08;Point-to-Point Protocol&#xff09;是一种数据链路层协议&#xff0c;用于在两个节点之间建立点对点的数据通信连接。PPP协议是TCP/IP协议族中的一员&#xff0c;它可以在串行通信线路上传输IP数据包&#xff0c;支持多种网络层协议&#xff…

C++ Primer 第11章关联容器

11.1 使用关联容器 map类型通常被常被称为关联数组。关联数组与正常数组类似&#xff0c;不同之处在于其下标不必是整数set就是关键字的简单集合&#xff0c;当想知道一个值是否存在时&#xff0c;set是最有用的 使用map #include<iostream> #include<string> #…

智慧水务物联网数据采集平台和营收管理平台建设

平台概述 智慧水务物联网数据采集平台是以物联感知技术、大数据、智能控制、云计算、人工智能、数字孪生、AI算法、虚拟现实技术为核心&#xff0c;以监测仪表、通讯网络、数据库系统、数据中台、模型软件、前台展示、智慧运维等产品体系为支撑&#xff0c;以城市水资源、水生…

MySQL - 第10节 - MySQL索引特性

1.索引的概念 索引的概念&#xff1a; • 数据库表中存储的数据都是以记录为单位的&#xff0c;如果在查询数据时直接一条条遍历表中的数据记录&#xff0c;那么查询的时间复杂度将会是O(N)。 • 索引的价值在于提高海量数据的检索速度&#xff0c;只要执行了正确的创建索引的操…