创建型模式-单件(Singleton)

转自:http://blog.vckbase.com/arong/archive/2004/05/25/280.html
注意:我的文章一般都是倾向于原理性介绍,内含代码可能存在错漏,也绝对不是大家可以拿来就用的标准代码。模式设计重要在于思想,而不在于如何实现。

考虑“创建型模式-工厂方法(Factory method)”中,我们对于每种虚构造器对象,我们都只需要一个实例。类似的情况还在许多地方出现,概括起来就是:某种类型,只需要也只能被实例化一次,这种只能有单一实例的类型称为一个单件类,或者说这种编程模式为单件模式。

可以使用全局对象方式来定义一个类型的实例,并通过约定来保证它的单一性。这对于小的程序尤其是一个人写的程序往往是可行的,但是对于大的项目则往往不行。项目中的组成人员素质是参差不齐的,他们对项目中各种各样的约定也不了解,在以下三种情况下,约定往往会被打破:

  1. 组员不尊重约定,我行我素编程
  2. 组员疏忽,忘记了约定
  3. 新进组员,根本不知道有这个约定

这种情况下,使用约定的方式保证单一性是很明显不可能的。为了保证单一性,必须通过编译器提供的机制来实现。

由于每个类被实例化时,实例化的代码必须能访问构造函数,如果构造函数不是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)构造初始化。

使用单件有如下好处:

  1. 只有一个单一实例,可以避免占有额外的地址空间,也可以避免由于多实例导致的冲突(冲突的例子如:两个COM通讯的实例同时访问同一个COM端口)
  2. singleton类的实例不在全局变量空间中,避免污染全局变量空间
  3. 一般情况下,这些实例都是在使用时创建,实现按需分配
  4. 可控的类实例化,将使得程序设计者能够更精确的控制类的行为

单件也可以扩展到实例数量有限的情况,这里就不再赘述,只要读者能清晰单件的原理,这些都不是很困难。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值