设计模式之单例模式

一、单例模式概念

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

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

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

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

  • 将默认构造函数设为私有 , 防止其他对象使用单例类的 new运算符。
  • 新建一个静态构建方法作为构造函数。 该函数会 "偷偷" 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

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

  • 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。
  • 如果你需要更加严格地控制全局变量, 可以使用单例模式。 单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

单例设计模式的结构

单例 (Singleton) 类声明了一个名为 get­Instance获取实例的静态方法来返回其所属类的一个相同实例。单例的构造函数必须对客户端 (Client) 代码隐藏。 调用 获取实例方法必须是获取单例对象的唯一方式。

代码如下:

问题:对于一些类来说,只有一个实例是很重要的。例如数据库或其共享资源的访问权限。并且这个实例需要易于被访问。

解决方案:保证一个类只有一个实例,并提供一个访问它的全局访问点。
单例模式实现的三种方法:(具体实现看代码)

1.懒汉式:你不找懒汉,懒汉根本就懒得去初始化自己。

优点:

缺点:当有两个线程调 getInstance() 方法,当它们同时执行到 if (null == instance) 这行代码,instancenull

2.饿汉式:饿汉根本等不及别人来找他,不管三七二十一先初始化了自身的实例,生怕自己饿着了。

优点:规避了懒汉式方法的线程问题,不用显示编写线程安全代码。

缺点:在没有必要获取实例时,已经预先产生了开销。

3.双重锁的形式:如果既不想在没有调用 getInstance() 方法时产生开销,又不想发生线程安全问题,就可以采用双重锁的形式。

问题:在外面判断了 instance 实例是否存在,为什么在锁定后又要在内部又判断一次?

这是因为,如果 instancenull 时有两个线程同时调用 getInstance(),由于 synchronized 机制,只允许一个线程进入,另一个需要等待。这时如果没有第二道 instance 是否为 null 的判断,就可能发生第一个线程创建一个实例,而第二个线程又创建一个实例的情况。

#include<iostream>
#include<mutex>
#include<thread>

using namespace std;
class Singleton
{
private:
    Singleton(const std::string value) : m_value(value)
    {

    }
    ~Singleton(){}
    string m_value;
public:
    Singleton(Singleton& other)=delete;
    Singleton(const Singleton& other)=delete;
    Singleton& operator=(const Singleton& other)=delete;
    string value()const{return m_value;}
    static Singleton* getInstance(const std::string& value);
private:
    static Singleton* m_instance;
    static mutex m_mutex;
};
//静态函数在类外初始化
Singleton* Singleton::m_instance = nullptr;
mutex Singleton::m_mutex;

Singleton* Singleton::getInstance(const std::string& value)
{
    //方法1:懒汉式:在单例对象第一次被使用时,才会创建实例。这种方式在单例对象未被使用时不会立即创建实例,这样可以节省资源。
    /*lock_guard<mutex> lock(m_mutex);
    if (m_instance == nullptr)
    {
        m_instance = new Singleton(value);
    }
    return m_instance;*/

    //方法2:饿汉式:在程序启动时,单例对象就被创建并初始化。这种方式在单例对象被第一次使用时会立即创建实例,因此不需要考虑线程安全问题。
    /*static Singleton* s = new Singleton(value);
    return s;*/

    //方法3:双重检查锁定:在多线程环境下,使用双重检查锁定来确保只有一个线程能够创建单例对象。这种方式在单例对象
    //未被使用时不会立即创建实例,可以节省资源,同时也能保证线程安全。
    if (m_instance == nullptr)
    {
        lock_guard<mutex> lock(m_mutex);
        if (m_instance == nullptr)
        {
            m_instance = new Singleton(value);
        }
    }
    return m_instance;
}

void Cat()
{
    Singleton* s = Singleton::getInstance("cat");
    cout << s->value() << "\n";
}

void Dog()
{
    Singleton* s = Singleton::getInstance("Dog");
    cout << s->value() << "\n";
}

int main()
{
    thread t1(Cat);
    thread t2(Dog);

    t1.join();
    t2.join();
    return 0;
}

二、单例模式的优缺点

单例模式的优点:

  • 你可以保证一个类只有一个实例。

  • 你获得了一个指向该实例的全局访问节点。

  • 仅在首次请求单例对象时对其进行初始化。
    单例模式的缺点:

  • 违反了单一职责原则。 该模式同时解决了两个问题。

  • 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。

  • 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。

  • 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。

相关推荐
Lenyiin36 分钟前
《 C++ 修炼全景指南:十 》自平衡的艺术:深入了解 AVL 树的核心原理与实现
数据结构·c++·stl
程序猿练习生1 小时前
C++速通LeetCode中等第5题-无重复字符的最长字串
开发语言·c++·leetcode
南郁1 小时前
把设计模式用起来!(3)用不好模式?之时机不对
设计模式
无名之逆1 小时前
云原生(Cloud Native)
开发语言·c++·算法·云原生·面试·职场和发展·大学期末
好蛊2 小时前
第 2 课 春晓——cout 语句
c++·算法
Lill_bin3 小时前
Lua编程语言简介与应用
开发语言·数据库·缓存·设计模式·性能优化·lua
景小雨4 小时前
【数据结构与算法】排序算法之快速排序(简)
c++·算法·排序算法·快速排序
鸽嗷高.5 小时前
C++伟大发明--模版
开发语言·c++
瞅瞅水5 小时前
设计模式中工厂模式的C语言实现
设计模式
weixin_486681146 小时前
C++系列-STL容器中统计算法count, count_if
开发语言·c++·算法