引言
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。单实例模式的UML图如下所示。
一个类只能产生一个对象,该怎么实现呢?我们知道,可以通过类定义对象创建实例,或者通过new关键字产生对象;这个该怎么控制?
创建对象时,都会根据输入的参数调用相应的构造函数,如果我们把构造函数设置为private私有访问权限不就不可以创建对象了吗?
还有个问题:那我总要创建一个对象啊,单实例啊,不是无实例!咋办?有办法!可以定义一个公有的函数GetInstance()用于返回对象,这样就能创建辣!
这又出现问题了,那这样做和将构造函数公有化有什么区别?你还是可以通过GetInstance()函数来创建多个对象...
咋办?能不能只创建一次,设置个标志位?再次创建的时候判断一下?那得保证创建的这个变量的生存期不随对象的创建和销毁而变化,有了! 用static关键字声明吧。
因为GetInstance()的返回类型为对应的类,所以需要定义一个含有该类的静态私有对象。
举例说明一下:
皇帝一般来说,一个时期只存在一个,下面用大臣参拜皇帝的例子来说明单实例模式。CEmperor代表皇帝类,大臣在主函数中参拜皇帝。
1 /* 2 Time:2016-9-25 18:26:39 3 Author:CodingMengmeng 4 Description:Singleton. 5 */ 6 #include <iostream> 7 #include <tchar.h> 8 using namespace std; 9 10 class CEmperor{ 11 private: 12 CEmperor();//注意:构造函数私有 13 virtual ~CEmperor(); 14 static CEmperor* instance;//唯一实例 15 16 public: 17 static CEmperor* GetInstance();//工厂方法(用来获得实例) 18 static void emperorSay(void);//类中其它方法,尽量是static 19 20 }; 21 22 CEmperor::CEmperor() 23 { 24 //世俗和道德约束你,目的就是不希望产生第二个皇帝 25 cout << "CEmperor Constructor" << endl; 26 } 27 28 CEmperor::~CEmperor() 29 { 30 if (instance != NULL) 31 delete instance; 32 } 33 34 //静态成员变量必须在类外初始化 35 CEmperor* CEmperor::instance = NULL; 36 37 CEmperor* CEmperor::GetInstance() 38 { 39 if (instance == NULL) //如果未创建过,则new一个出来,如果new过了,则直接返回 40 instance = new CEmperor(); 41 return instance; 42 } 43 44 void CEmperor::emperorSay() 45 { 46 cout << "我就是皇帝某某某...." << endl; 47 } 48 49 class CMinister{ 50 51 }; 52 53 int _tmain(int argc, _TCHAR* argv[]) 54 { 55 for (int day = 0; day < 3; day++) 56 { 57 CEmperor* emperor = CEmperor::GetInstance();//当GetInstance()为static时,才能保证实例由类本身来创建,否则一个非static成员函数必须与特定对象搭配才能调用 58 emperor->emperorSay(); 59 } 60 61 //三天见的皇帝都是同一个人。 62 return 0; 63 }
运行结果:
分析:
当第一次调用GetInstance()时,instance为NULL,所以会执行instance=new Emperor();把这个新建的实例保存到静态成员instance,并返回这个指针。
第二次到第N次调用GetInstance()时,由于instance不为空,所以会直接返回instance。也就是第一次调用GetInstance创建的那个实例,这样就实现了单实例。
要点
显然单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。上面的例子中,
static CEmperor* instance;
即表示要点二:类定义中含有一个该类的静态私有对象;
static CEmperor* GetInstance();
即表示要点三:该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
单实例的应用
1、单实例模式的优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的被创建、销毁,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显;
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
- 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
2、单实例模式的缺点
- 单例模式没有接口,扩展很困难,若要扩展,除了修改代码没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何的意义,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。
- 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
- 单例模式与单一职责原则有冲突。一个类应该只实现一个的逻辑,而不关心它是否是单例的,决定它是不是要单例是环境决定的,单例模式把“要单例”和业务逻辑融合也在一个类中。
3、单实例模式的使用场景
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”时,则可以采用单例模式,具体的场景如下:
- 要求生成唯一序列号的环境;
- 在整个项目中需要有访问一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
- 创建一个对象需要消耗的资源过多,如要访问IO、访问数据库等资源;
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式);
4、单实例模式的注意事项
在高并发情况下,请注意单例模式的线程同步问题。