1.单例模式(Singleton)
单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但却会破坏代码的模块化特性。
在某些其他上下文中, 你不能使用依赖于单例的类。 你也将必须使用单例类。 绝大多数情况下, 该限制会在创建单元测试时出现。
实现单例模式必须注意一下几点:
- 单例类只能由一个实例化对象。
- 单例类必须自己提供一个实例化对象。
- 单例类必须提供一个可以访问唯一实例化对象的接口。
单例模式分为懒汉和饿汉两种实现方式。
1.1 懒汉单例模式
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化一个对象。在访问量较小,甚至可能不会去访问的情况下,采用懒汉实现,这是以时间换空间。
1.1.1 非线程安全的懒汉单例模式
class Singleton { protected: Singleton(const std::string value): value_(value) { } static Singleton* singleton_; std::string value_; public: Singleton(Singleton &other) = delete; void operator=(const Singleton &) = delete; static Singleton *GetInstance(const std::string& value); std::string value() const{ return value_; } }; Singleton* Singleton::singleton_= nullptr;; Singleton *Singleton::GetInstance(const std::string& value) { if(singleton_==nullptr){ singleton_ = new Singleton(value); } return singleton_; } |
1.1.2、线程安全的懒汉单例模式
//线程安全版,有性能问题 class Singleton { private: static Singleton * pinstance_; static std::mutex mutex_; protected: Singleton(const std::string value): value_(value) { } ~Singleton() {} std::string value_; public: Singleton(Singleton &other) = delete; void operator=(const Singleton &) = delete; static Singleton *GetInstance(const std::string& value); std::string value() const{ return value_; } }; Singleton* Singleton::pinstance_{nullptr}; std::mutex Singleton::mutex_; Singleton *Singleton::GetInstance(const std::string& value) { std::lock_guard lock(mutex_); if (pinstance_ == nullptr) { pinstance_ = new Singleton(value); } return pinstance_; } |
//优化版,双重检查 std::mutex mt; class Singleton { public: static Singleton* getInstance(); private: Singleton(){} //构造函数私有 Singleton(const Singleton&) = delete; //明确拒绝 Singleton& operator=(const Singleton&) = delete; //明确拒绝 static Singleton* m_pSingleton;
}; Singleton* Singleton::m_pSingleton = NULL; Singleton* Singleton::getInstance() { if(m_pSingleton == NULL) { mt.lock(); if(m_pSingleton == NULL) { m_pSingleton = new Singleton(); } mt.unlock(); } return m_pSingleton; } |
1.1.3、返回一个reference指向local static对象
这种单例模式实现方式多线程可能存在不确定性:任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。解决的方法:在程序的单线程启动阶段手工调用所有reference-returning函数。这种实现方式的好处是不需要去delete它。
class Singleton { public: static Singleton& getInstance(); private: Singleton(){} Singleton(const Singleton&) = delete; //明确拒绝 Singleton& operator=(const Singleton&) = delete; //明确拒绝 }; Singleton& Singleton::getInstance() { static Singleton singleton; return singleton; } |
1.2、饿汉单例模式
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
//饿汉式:线程安全,注意一定要在合适的地方去delete它 class Singleton { public: static Singleton* getInstance(); private: Singleton(){} //构造函数私有 Singleton(const Singleton&) = delete; //明确拒绝 Singleton& operator=(const Singleton&) = delete; //明确拒绝 static Singleton* m_pSingleton; }; Singleton* Singleton::m_pSingleton = new Singleton(); Singleton* Singleton::getInstance() { return m_pSingleton; } |
1.3 单例模式优缺点:
优点 | 缺点 |
可以保证一个类只有一个实例。 | 违反了_单一职责原则_。 该模式同时解决了两个问题。 |
获得了一个指向该实例的全局访问节点 | 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。 |
仅在首次请求单例对象时对其进行初始化 | 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。 |
单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。 |