单例模式是什么?
单例模式是一种创建型的软件设计模式。通过单例模式的设计,使得创建的类在当前进程中只有唯一一个实例,并提供一个全局性的访问点,这样可以规避因频繁创建对象而导致的内存飙升情况。
适用场景
- 如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式。
单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。该方法可以创建一个新对象,但如果该对象已经被创建,则返回已有的对象。
- 如果你需要更加严格地控制全局变量,可以使用单例模式。
单例模式与全局变量不同,它保证类只存在一个实例。除了单例类自己以外,无法通过任何方式替换缓存的实例。请注意, 你可以随时调整限制并设定生成单例实例的数量,只需修改获取实例方法, 即getInstance 中的代码即可实现。
单例模式要点
- 在类中添加一个私有静态成员变量用于保存单例实例。
- 声明一个公有静态构建方法用于获取单例实例。
- 在静态方法中实现"延迟初始化"。该方法会在首次被调用时创建一个新对象,并将其存储在静态成员变量中。此后该方法每次被调用时都返回该实例。
- 将类的构造函数设为私有。类的静态方法仍能调用构造函数,但是其他对象不能调用。
- 检查客户端代码,将对单例的构造函数的调用替换为对其静态构建方法的调用。
优点
- 你可以保证一个类只有一个实例。
- 你获得了一个指向该实例的全局访问节点。
- 仅在首次请求单例对象时对其进行初始化。
缺点
- 违反了单一职责原则。该模式同时解决了两个问题。
- 单例模式可能掩盖不良设计,比如程序各组件之间相互了解过多等。
- 该模式在多线程环境下需要进行特殊处理,避免多个线程多次创建单例对象。
- 单例的客户端代码单元测试可能会比较困难,因为许多测试框架以基于继承的方式创建模拟对象。由于单例类的构造函数是私有的,而且绝大部分语言无法重写静态方法,所以你需要想出仔细考虑模拟单例的方法。要么干脆不编写测试代码,或者不使用单例模式。
基础代码
h文件:
//
// Created by qiaowei on 2023-12-25.
//
#ifndef RADARDATACONTROLLER_MAIN_WINDOW_HANDLER_H
#define RADARDATACONTROLLER_MAIN_WINDOW_HANDLER_H
#include <QObject>
namespace handler {
class MainWindowHandler : public QObject{
public:
virtual ~MainWindowHandler() override;
static MainWindowHandler* instance();
private:
explicit MainWindowHandler(QObject* parent = nullptr);
private:
/**
* @date 2023-12-25 19:32
* @author qiao wei
* @brief MainwindowHandler实例指针。static函数只能访问静态成员变量或函数,所以必须为static
*/
static MainWindowHandler* instance_;
};
} // handler
#endif //RADARDATACONTROLLER_MAIN_WINDOW_HANDLER_H
cpp文件:
//
// Created by qiaowei on 2023-12-25.
//
#include "main_window_handler.h"
namespace handler {
// 静态非const整型成员变量必须在类外定义。
MainWindowHandler* MainWindowHandler::instance_ = nullptr;
MainWindowHandler::MainWindowHandler(QObject* parent) : QObject(parent) {}
MainWindowHandler::~MainWindowHandler() {}
MainWindowHandler* MainWindowHandler::instance() {
if (nullptr == instance_) {
instance_ = new MainWindowHandler{nullptr};
}
return instance_;
}
} // handler
单例模式一般分为懒汉式和饿汉式。
单例模式的基础代码不符合线程安全,因此引出了懒汉式和饿汉式单例模式。懒汉式:指全局的单例实例在第一次被使用时构建。饿汉式:全局的单例实例在类装载(ClassLoader)时构建(饿汉式单例性能优于懒汉式单例)。
懒汉式和饿汉式的区别:
- 懒汉式默认不会实例化,外部什么时候调用什么时候new。饿汉式在类加载的时候就实例化,并且创建单例对象。
- 懒汉式是延时加载,在需要的时候才创建对象,而饿汉式是在虚拟机启动的时候就会创建。
- 懒汉式在多线程中是线程不安全的,而饿汉式是不存在多线程安全问题的。
懒汉单例模式(需要时再实例化单例对象)
懒汉模式中使用QMutex,QMutexLocker进行同步锁。
h文件:
//
// Created by qiaowei on 2023-12-25.
//
#ifndef RADARDATACONTROLLER_MAIN_WINDOW_HANDLER_H
#define RADARDATACONTROLLER_MAIN_WINDOW_HANDLER_H
#include <QObject>
namespace handler {
class MainWindowHandler : public QObject{
public:
/**
* @date 2023-12-25 21:54
* @author qiao wei
* @version 1.0
* @brief 拷贝构造函数。删除拷贝构造,防止对象通过拷贝构造创建对象。
* @param
* @return
* @throws
*/
MainWindowHandler(const MainWindowHandler& value) = delete;
virtual ~MainWindowHandler() override;
/**
* @date 2023-12-25 21:56
* @author qiao wei
* @version 1.0
* @brief 赋值操作符。删除赋值操作符,防止简单类型通过赋值创建对象。
* @param
* @return
* @throws
*/
MainWindowHandler& operator=(const MainWindowHandler& value) = delete;
/**
* @date 2023-12-25 21:47
* @author qiao wei
* @version 1.0
* @brief 返回MainWindowHandler*指针的static函数。
* @param
* @return Mainwindow*指针。
* @throws
*/
static MainWindowHandler* instance();
private:
explicit MainWindowHandler(QObject* parent = nullptr);
private:
/**
* @date 2023-12-25 19:32
* @author qiao wei
* @brief MainwindowHandler实例指针。
*/
static MainWindowHandler* instance_;
static QMutex mutex_;
};
} // handler
#endif //RADARDATACONTROLLER_MAIN_WINDOW_HANDLER_H
cpp文件:
//
// Created by qiaowei on 2023-12-25.
//
#include <QMutexLocker>
#include <QMutex>
#include "main_window_handler.h"
namespace handler {
// 静态成员在类外初始化。
MainWindowHandler* MainWindowHandler::instance_ = nullptr;
QMutex MainWindowHandler::mutex_;
MainWindowHandler::~MainWindowHandler() {
delete instance_;
instance_ = nullptr;
}
MainWindowHandler* MainWindowHandler::instance() {
// 第1次检查:实例化单例对象后,就不会再进入加锁逻辑。
if (nullptr == instance_) {
// 加同步锁。
QMutexLocker mutex_locker(&mutex_);
// 第2次检查:可能2个线程同时通过第1次检查,1个线程获得锁时,可能另外1个线程已经实例化单体。
if (nullptr == instance_) {
instance_ = new MainWindowHandler{nullptr};
}
}
return instance_;
}
MainWindowHandler::MainWindowHandler(QObject* parent) : QObject(parent) {}
} // handler
饿汉单例模式(一开始就初始化单例对象)
饿汉模式一
饿汉模式一缺点:不会调用析构函数,需要手动delete。
h文件
//
// Created by qiaowei on 2023-12-25.
//
#ifndef RADARDATACONTROLLER_EAGER_SINGLETON_H
#define RADARDATACONTROLLER_EAGER_SINGLETON_H
#include <QObject>
namespace handler {
class EagerSingleton : public QObject{
public:
virtual ~EagerSingleton() override;
static EagerSingleton* instance();
private:
explicit EagerSingleton(QObject* parent = nullptr);
private:
static EagerSingleton* instance_;
};
} // handler
#endif //RADARDATACONTROLLER_EAGER_SINGLETON_H
cpp文件
//
// Created by qiaowei on 2023-12-25.
//
#ifndef RADARDATACONTROLLER_EAGER_SINGLETON_H
#define RADARDATACONTROLLER_EAGER_SINGLETON_H
#include <QObject>
namespace handler {
class EagerSingleton : public QObject{
public:
virtual ~EagerSingleton() override;
static EagerSingleton* instance();
private:
explicit EagerSingleton(QObject* parent = nullptr);
private:
static EagerSingleton* instance_;
};
} // handler
#endif //RADARDATACONTROLLER_EAGER_SINGLETON_H
饿汉模式二
h文件
//
// Created by qiaowei on 2023-12-25.
//
#ifndef RADARDATACONTROLLER_EAGER_SINGLETON_2_H
#define RADARDATACONTROLLER_EAGER_SINGLETON_2_H
#include <QObject>
namespace handler {
class EagerSingleton2 : public QObject{
public:
static EagerSingleton2* instance();
virtual ~EagerSingleton2();
private:
explicit EagerSingleton2(QObject* parent = nullptr);
private:
static EagerSingleton2 eager_singleton_;
};
} // handler
#endif //RADARDATACONTROLLER_EAGER_SINGLETON_2_H
cpp文件
//
// Created by qiaowei on 2023-12-25.
//
#include "eager_singleton_2.h"
namespace handler {
EagerSingleton2 EagerSingleton2::eager_singleton_;
EagerSingleton2* EagerSingleton2::instance() {
return &eager_singleton_;
}
EagerSingleton2::~EagerSingleton2() {}
EagerSingleton2::EagerSingleton2(QObject *parent) : QObject(parent) {}
} // handler
饿汉模式总结:
由于在定义静态变量的时候实例化单例类,因此在类加载的时候就已经创建了单例对象,可确保单例对象的唯一性。
饿汉模式缺点:
无论系统运行时是否需要使用该单例对象,都会在类加载时创建对象,资源利用效率不高。