一.意图
单例是一种创建设计模式,允许你确保一个类只有一个实例,同时提供对该实例的全局访问点。
保证一个类仅有一个实例,并提供一个该实例的全局访问点。 ------《设计模式》GoF
二.问题
单例模式同时解决了两个问题,违反了单一责任原则:
-
确保一个类只有一个实例。为什么有人会想控制一个类有多少实例?最常见的原因是控制对某些共享资源的访问------例如数据库或文件。
原理是这样的:想象你创建了一个对象,但过了一段时间后决定创建一个新的。你不会收到一个全新的物品,而是你已经创建好的那个。
注意,这种行为无法用普通构造函数实现,因为构造函数调用必须从设计上返回新对象。
-
提供一个全局访问点到该实例。还记得你(好吧,是我)用来存储一些重要对象的全局变量吗?虽然它们非常方便,但也非常不安全,因为任何代码都有可能覆盖这些变量的内容,导致应用崩溃。
就像全局变量一样,单例模式允许你从程序中的任意位置访问某个对象。不过,它也保护该实例不被其他代码覆盖。
这个问题还有另一面:你不希望解决问题#1的代码散落在你的程序中。最好放在一个类里,尤其是当你的其他代码已经依赖它时。
如今,单例模式已非常流行,以至于即使某样东西只解决了上述一个问题,人们也可能称之为单例。
三.解决方案
所有单例实现都具有以下两个共同步骤:
-
将默认构造子设为私有,以防止其他对象使用带有Singleton类的作符。
new -
创建一个静态的创建方法,作为构造函数。在底层,这种方法调用私有构造器创建对象,并将其保存在静态字段中。所有后续对该方法的调用都会返回缓存对象。
如果你的代码能访问单例类,那么它就能调用单例的静态方法。所以每当调用该方法时,总是返回同一个对象。
四.结构

五.适合应用场景
-
当程序中的某个类应仅对所有客户端开放单一实例时,使用单例模式;例如,程序的不同部分共享的单个数据库对象。
单例模式禁用了除特殊创建方法外的所有类对象创建方式。该方法要么创建新对象,要么返回已创建的对象。
-
当你需要更严格控制全局变量时,可以使用单例模式。
与全局变量不同,单例模式保证一个类只有一个实例。除了单例类本身,没有任何东西能替代缓存实例。
请注意,你总可以调整这个限制,允许创建任意数量的单例实例。唯一需要修改的代码是方法的正文。
getInstance
六.实现方式
-
在类中添加一个私有静态字段用于存储单例实例。
-
声明一个公开的静态创建方法来获取单例实例。
-
在静态方法中实现"懒惰初始化"。它应该在第一次调用时创建一个新对象,并将其放入静态字段。该方法应在所有后续调用中始终返回该实例。
-
让类的构造器设为私密。类的静态方法仍然可以调用构造子,但不能调用其他对象。
-
检查客户端代码,将所有直接调用单例构造函数的调用替换为对其静态创建方法的调用。
七.优缺点
-
优点:
-
你可以确定一个类只有一个实例。
-
你会获得一个全局访问点。
-
单例对象只有在首次被请求时才会被初始化。
-
-
缺点
-
违反了单一责任原则。该图案在当时解决了两个问题。
-
单例模式可以掩盖不良设计,例如当程序组件彼此了解过多时。
-
在多线程环境中,这种模式需要特殊处理,以避免多个线程多次创建单例对象。
-
由于许多测试框架在生成模拟对象时依赖继承,单例客户端代码的单元测试可能较为困难。由于单例类的构造子是私有的,且大多数语言中无法覆盖静态方法,你需要想出一种有创意的方法来模拟单例。或者干脆不参加考试。或者不要用单例模式。
-
八.与其他模式的关系
-
一个门面类通常可以转换为单例,因为大多数情况下单个门面对象就足够了。
-
如果你设法将所有共享状态的物体简化为一个蝇量级物体,蝇量级会类似于单例。但这些模式之间有两个根本区别:
-
单例体应当只有一个,而轻量级可以有多个具有不同内在状态的实例。
-
单点对象可以是可变的。蝇量级对象是不可变的。
-
-
抽象工厂、建造者和原型都可以作为单例实现。
九.示例代码
/**
* 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;
}
执行结果
If you see the same value, then singleton was reused (yay!
If you see different values, then 2 singletons were created (booo!!)
RESULT:
BAR
FOO