确保对象的唯一性——单例模式
单例模式的动机
对于一个软件系统中的某些类而言,无须创建多个实例。举个大家都熟知的例子——Windows任务管理器,如图3-1所示。可以做一个这样的尝试:在Windows任务栏的右键弹出菜单上多次单击“启动任务管理器”,看能否打开多个任务管理器窗口(注:计算机中毒或私自修改Windows内核者除外)。在正常情况下,无论启动任务管理器多少次,Windows系统始终只能弹出一个任务管理器窗口,也就是说,在一个Windows系统中,任务管理器存在唯一性。为什么要这样设计呢?可以从以下两个方面来分析:其一,如果能弹出多个窗口,且这些窗口的内容完全一致,全部是重复对象,这势必会浪费系统资源(任务管理器需要获取系统运行时的诸多信息,这些信息的获取需要消耗一定的系统资源,包括CPU资源及内存资源等),而且根本没有必要显示多个内容完全相同的窗口;其二,如果弹出的多个窗口内容不一致,问题就更加严重了,这意味着在某一瞬间系统资源使用情况和进程、服务等信息存在多个状态,例如任务管理器窗口A显示“CPU使用率”为10%,窗口B显示“CPU使用率”为15%,到底哪个才是真实的呢?这会给用户带来误解,更不可取。由此可见,确保Windows任务管理器在系统中有且仅有一个非常重要。
在实际开发中也经常遇到类似的情况,为了节约系统资源,有时需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后,无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性,可以通过单例模式来实现,这就是单例模式的动机所在。
单例模式概述
下面来模拟实现Windows任务管理器。假设任务管理器的类名为TaskManager,在TaskManager类中包含了大量的成员方法,例如构造函数TaskManager(),显示进程的方法displayProcesses(),显示服务的方法displayServices()等,该类的示意代码如下:
class TaskManager {
public:
TaskManager() {
...}
void displayProcesses() {
...}
void displayServices() {
...}
...
}
为了实现Windows任务管理器的唯一性,通过以下3步对TaskManager类进行重构:
(1)由于每次使用new关键字来实例化TaskManager类时都将产生一个新对象,为了确保TaskManager实例的唯一性,需要禁止类的外部直接使用new来创建对象,因此需要将TaskManager的构造函数的可见性改为private,代码如下:
private:
TaskManager() {
...}
(2)将构造函数的可见性改为private后,虽然类的外部不能再使用new来创建对象,但是在TaskManager的内部还是可以创建对象的,可见性只对类外有效。因此,可以在TaskManager中创建并保存这个唯一实例。为了让外界可以访问这个唯一实例,需要在TaskManager中定义一个静态的TaskManager类型的私有成员变量,代码如下:
private:
static TaskManager* tm = nullptr;
(3)为了保证成员变量的封装性,将TaskManager类型的tm对象的可见性设置为private,但外界该如何使用该成员变量并何时实例化该成员变量呢?答案是增加一个公有的静态方法,代码如下:
public:
static TaskManager* getInstance() {
if (tm == nullptr)
tm = new TaskManager();
return tm;
}
在getInstance()方法中首先判断tm对象是否存在,如果不存在(即tm==nullptr为true),则使用new关键字创建一个新的TaskManager类型的tm对象,再返回新创建的tm对象;否则直接返回已有的tm对象。
需要注意的是getInstance()方法的修饰符,首先它应该是一个public方法,以便外界其他对象使用;其次它使用了static关键字,即它是一个静态方法,在类外可以直接通过类名来访问,而无须创建TaskManager对象。事实上,在类外也无法创建TaskManager对象,因为构造函数是私有的。
通过以上3个步骤,完成了一个最简单的单例类的设计,其完整代码如下:
class TaskManager {
private:
TaskManager() {
}
public:
void displayProcesses() {
}
void displayServices() {
}
static TaskManager* getIns