考虑“创建型模式-工厂方法(Factory method)”中,我们对于每种虚构造器对象,我们都只需要一个实例。类似的情况还在许多地方出现,概括起来就是:某种类型,只需要也只能被实例化一次,这种只能有单一实例的类型称为一个单件类,或者说这种编程模式为单件模式。
可以使用全局对象方式来定义一个类型的实例,并通过约定来保证它的单一性。这对于小的程序尤其是一个人写的程序往往是可行的,但是对于大的项目则往往不行。项目中的组成人员素质是参差不齐的,他们对项目中各种各样的约定也不了解,在以下三种情况下,约定往往会被打破:
这种情况下,使用约定的方式保证单一性是很明显不可能的。为了保证单一性,必须通过编译器提供的机制来实现。
由于每个类被实例化时,实例化的代码必须能访问构造函数,如果构造函数不是public的,则实例化就会被禁止。为此,可以把单件类的构造函数设置成private或者protected的,这样使用者就不能任意的实例化该类。对于一个简单的单件,它一般不需要被继承,这种情况下,把构造函数设置成private是最好的(这样禁止继承)。
既然我们限制了构造函数,主动维护对象的实例化过程,那么同时维护析构过程也是一个合理的习惯。因此,建议大家使析构函数的访问修饰和构造函数的一致。即构造函数是private的,析构函数也是private,如果构造函数是protected的,则析构函数也是protected的。
下面以单件类CSingleton来说明,首先说明补需要派生的简单单件。
对于简单的单件,首先需要把构造函数和析构函数定义成private的,因此它首先被定义成如下所示的类:
class CSingleton { private: CSingleton(); virtual ~CSingleton(); };
由于我们隐藏了构造和析构函数,还必须为这个类提供创建自己实例和删除实例的接口。对于创建实例,我们可以用一个static成员函数实现(由于在这个接口函数被调用之前,还不存在任何实例,因此该函数必须是static的),对于删除实例,则可以用一个Delete成员函数来实现。为了保证实例的单一性,我们还必须保存实例的指针,为此,我们需要一个静态指针成员变量来保存实例的地址。增加了这些接口的类如下:
//头文件 class CSingleton { private: static CSingleton * s_pSingleton; private: CSingleton(); virtual ~CSingleton(); public: static CSingleton* Instance();//创建接口 void Delete();//删除接口 }; //CPP文件 CSingleton * CSingleton ::s_pSingleton = (CSingleton*)0; CSingleton * CSingleton ::Instance() { if((CSingleton*)0 == s_pSingleton) { s_pSingleton = new CSingleton; } return s_pSingleton; } void CSingleton::Delete() { if((CSingleton*)0 != s_pSingleton) { //首先设置s_pSingleton,避免Instance函数出错 s_pSingleton =(CSingleton*)0; } delete this; }
对于简单的单件,以上所做的就足够了,用户可以在此基础上添加自己的成员函数和变量进行处理。
考虑我们在“工厂方法”一文中所述,对于每个CShape类派生类,我们都需要给他们创建一个虚构造器,这一组构造器都应该是单件类。如果我们按照前文方法对每个虚构造器都进行单件设计,也不是不可以,但是这样将非常复杂。采用一种“注册”机制来做,将降低工作量。
为了实现这一目标,首先为这些单件类定义一个共同的根,在“工厂方法”中,该根就是CShareRuntimeClass,这个根本身不需要也不能被实例化,但是为了保证派生类的单件性,这个根的构造函数和析构函数必须保证是protected的。我们建立一个全局的表格,保存所有被实例化的子类对象的指针,为了保证单一性,这些子类都被分配一个唯一的ID号。由基类的构造函数在这个表格中注册这个实例化的类。为此,修改CSingleton类如下:
class CTableSingleton; class CSingletonPtr { CTableSingleton *m_pSingleton; public: CSingletonPtr(){m_pSingleton = NULL;}; operator CTableSingleton * & (){return m_pSingleton;}; }; class CTableSingleton { //使用CSingletonPtr的目的是能自动初始化该指针为NULL static CSingletonPtr s_apTable[MAX_OBJECTS];//全局表格 private: UINT m_uID;//对应子类的ID void RegisterSelf();//注册自己的函数 void UnregisterSelf();//反注册自己 protected: static CTableSingleton * FindInstance(UINT id);//根据ID从表格中寻找对应的实例 CTableSingleton(UINT id); virtual ~CTableSingleton(); }; CSingletonPtr CTableSingleton:: s_apTable[MAX_OBJECTS]; //注册函数 void CTableSingleton::RegisterSelf() { if(m_uID >= MAX_OBJECTS) { return; } g_apTable[m_uID]= this; } //反注册 void CTableSingleton::UnregisterSelf() { if(m_uID >= MAX_OBJECTS) { return; } g_apTable[m_uID]= NULL; } //构造函数 CTableSingleton::CTableSingleton(UINT uID):m_uID(uID) { RegisterSelf(); } //析构函数 CTableSingleton::~CTableSingleton(UINT uID):m_uID(uID) { UnregisterSelf(); } //从表格中搜索子类的函数 CTableSingleton* CTableSingleton::FindInstance(UINT uID) { if(uID >= MAX_OBJECTS) { return NULL; } return s_apTable[uID]; }
对于派生类而言,它必须隐藏自己的构造函数和析构函数,并提供一个实例化自己的接口。为了做到这一点,可以通过定义一些宏来实现,可以用下面宏实现目标:
#define DECLARE_SINGLETON(classname) / public:/ static classname * Instance();/ void Delete();/ private:/ classname(); #define IMPLEMENT_SINGLETON(classname,id) / classname * classname::Instance()/ {/ classname * pObj =static_cast(FindInstance(id)); if( pObj== static_cast(0))/ {//实例不存在/ return new classname;//实例化,基类会完成注册过程/ }else{/ return pObj;/ }/ }/ void classname::Delete()/ {/ delete this;//基类会完成反注册任务/ }/ / classname :: classname():CTableSingleton(id)
定义了这些宏以后,那么从CTableSingleton派生的类只要按照下列方式插入宏即可正确工作:
//头文件 class CDerivedClass:public CTableSingleton { DECLARE_SINGLETON(CDerivedClass); public://后面是其他定义 }; //CPP文件 IMPLEMENT_SINGLETON(classname,DERIVED_CLASS_ID) {//这里开始写构造函数处理过程 }
这里我们只提供了一个不带参数的构造函数,如果构造函数的参数固定,那么我们还可以提供方便的宏,如果不固定,只有一一设计了。但是注意的是,必须提供CTableSingleton(uID)构造初始化。
使用单件有如下好处:
单件也可以扩展到实例数量有限的情况,这里就不再赘述,只要读者能清晰单件的原理,这些都不是很困难。