在開發後台服務的過程中,我們常常需要從數據庫中取數據,並將數據緩存在本地中,另外,我們的服務還需要有更新數據的能力:包括定時的主動更新以及數據庫數據更新時服務收到通知的被動更新。
之前在需要用到以上功能的時候,模仿著組內通用的數據Cache部分的代碼來寫,十分方便,基本上只需要自己寫兩個類:一個是取數據並緩存數據的類XXXData,一個是扇出數據的類XXXFetcher。
在需要使用數據的時候,通過:
FetcherFactory::getFetcher()
即可獲取一個XXXFetcher對象的指針。
查看FetcherFactory.h的備注:
/**
* 配置中心, 存儲各種配置, 會自動搜索項目中的Fetcher實現類, 加載, 並
* 在收到刷新配置的通知時調用update方法
* 需要在服務器啟動的onInitialize中調用本類的Initialize方法進行初始化
*/
重點:會自動搜索項目中的Fetcher實現類並加載。
也就是說,FetcherFactory可以自動地查找項目中的Fetcher實現類,然後動態地創建XXXFethcer對象。
什麼叫動態創建呢?
就是能夠根據一個類的名字動態地創建該類的對象。
舉個例子,你的程序需要用到一個文本文件,而此文件中存放著一些類的名字,你能根據這些名字動態的生成這些類嗎?
/*****************************************************************************/
/*從文件中讀出類的名字存放在字符串變量szClassName中,現在假設讀出的字符串為 */
/* ”CString”,而類CString派生自CObject,那麼我們可以用基類指針指向派生類 */
/***********************************/*****************************************/
char szClassName[20]
CObject *pOb;
pOb = new szClassName; /* 概念版本 */
當然,以上只是一個概念版本,因為C++並不支持根據類名動態創建對象。C++中,操作符new後面只能跟數據結構的名稱,不能跟字符串。
那麼,有什麼方式來達到“根據名字動態生成類對象”的目的嗎?
最簡單粗暴的方法就是:用嵌套的if…else…語句,反復比較讀出的類名是否與預期的相同,然後生成此類的對象。的確,這是解決辦法之一,但是如果程序要用到的類很多,而且經常會改變或添加,豈不是要經常改寫識別字符串的函數?那樣的話,會很繁瑣且容易出錯。
那麼小組代碼裡的“動態創建”是如何實現的呢?
先看看Fetcher類的備注:
/**
* 自動加載的邏輯類, 會在FetcherFactory::Initialize中被自動加載, 在FetcherFactory::update時循環調用所有Fetcher::update方法
* 其實現類必須滿足下述條件:
* 1. 繼承自本類
* 2. 以Fetcher為類名結尾, 例如XXXFetcher, YYYFetcher
* 3. 在.h和.cpp文件中分別添加DECLARE_FETCHERHOLDER_DYNCREATE和IMPLEMENT_FETCHERHOLDER_DYNCREATE宏
* 4. 項目中不能有任何TC_DYN_Object子類同名
*
* 模擬MFC,動態生成類
*/
看來還是要先學習一下MFC動態生成類對象的方法。
下面我們簡單模擬下MFC的實現方法。
基礎知識:
class CExample
{
private:
int x;
public:
int y;
static long z;
… /*其它數據和函數*/
};
CExample j1,j2;
j1,j2兩個對象共用一份變量z,就是說,如果一個成員變量被冠以static,那麼無論這個類定義過多少對象,此成員變量在內存中始終只有一份。其實,變量z在類CExample還沒有定義任何對象之前就已經在內存中實際存在了。我們可以利用這一特點來實現類的動態識別和生成。
在MFC中,有一個特殊的類CRuntimeClass,它是實現上述功能最關鍵的地方。
CRuntimeClass的聲明:
class CObject; /* 聲明CObject是一個類,將在以後定義 */
struct CRuntimeClass
{
char *m_lpszClassName; /* 存放類名 */
int m_nObjectSize; /* 存放對象大小 */
CObject* (*m_pfnCreateObject) (); /* 指向函數的指針,這個函數返回一個CObject */
CRuntimeClass* m_pNextClass; /* 類型的指針 */
static CRuntimeClass* pFirstClass; /* 靜態變量,鏈表頭部 */
static CObject* CreateObject(const char* lpszClassName); /* 根據類名創建對象 */
};
接下來要的事情是:每個(我們想根據類名生成對象的)類都有一個靜態的CRuntimeClass成員變量(因此每個類有且只有一份),並且該變量有一定的命名規則,比如下面例子中的classXXX。然後,通過鏈表將這些變量串起來,進而可以通過CRuntimeClass::pFirstClass來獲取這些變量。
為了以後編程的方便性(同時也為了保密性),我們定義兩個宏:
/* 聲明 */
#define DECLARE_DYNAMIC(class_name)\
public:\
//定義CRuntimeClass成員變量
static CRuntimeClass class##class_name;\
//獲取CRuntimeClass變量的接口
virtual CRuntimeClass* GetRuntimeClass() const;
/* 實現 */
#define IMPLEMENT_DYNAMIC(class_name)\
//全局字符串,保存了本類的類名
char _lpsz##class_name[]=#class_name;\
//new一個本類的對象,非成員函數
CObject* Create##class_name()\
{ return new class_name; }\
//初始化CRuntimeClass靜態成員變量,將類名、上面的Create函數地址傳進去
CRuntimeClass class_name::class##class_name={\
_lpsz##class_name,sizeof(class_name),Create##class_name};\
//定義一個CLASS_INIT類型的對象,使用CRuntimeClass成員變量的地址構造
static CLASS_INIT _init_##class_name(&class_name::class##class_name);\
//返回CRuntimeClass成員變量的地址
CRuntimeClass* class_name::GetRuntimeClass() const\
{ return &class_name::class##class_name; }
出現在宏定義中的##告訴編譯器,把兩個字符串捆綁在一起。
其中CLASS_INIT定義如下:
struct CLASS_INIT
{
CLASS_INIT(CRuntimeClass* pNewClass);
};
CLASS_INIT 的構造函數實現如下:
CLASS_INIT::CLASS_INIT(CRuntimeClass* pNewClass)
{
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pNewClass;
}
於是各個類的CRuntimeClass成員變量都通過鏈表串聯起來了,鏈表頭為CRuntimeClass::pFirstClass(一個靜態成員變量)。
這樣,我們只要在類的定義文件(.h文件)中放入宏DECLARE_DYNAMIC,在類的實現文件(.cpp文件)中放入宏IMPLEMENT_DYNAMIC,就可以實現我們的目的。
舉個例子:
//CObject.h
class CObject{
DECLARE_DYNAMIC(CObject)
}
//CObject.cpp
IMPLEMENT_DYNAMIC(CObject)
編譯器做出來的是:
//CObject.h
class CObject{
public:
static CRuntimeClass classCObject;
virtual CRuntimeClass* GetRuntimeClass() const;
}
//CObject.cpp
char _lpszCObject[]=”CObject”;
CObject* CreateCObject()
{ return new CObject; }
CRuntimeClass CObject::classCObject={_lpszCObject,sizeof(class_name),CreateCObject};
static CLASS_INIT _init_CObject(&CObject::classCObject);
CRuntimeClass* CObject::GetRuntimeClass() const
{ return &CObject::classCObject; }
假設我們有3個類A,B,C,它們均繼承自CObject,並分別添加了兩個宏,當我們對代碼進行編譯之後,會發生什麼事情呢?
為類CRuntimeClass創建一個靜態變量pFirstClass,該變量為鏈表的頭指針; 為類A、B、C各創建一個CRuntimeClass類型的靜態變量; 每個類的.cpp文件中創建一個CLASS_INIT類型的靜態變量,在該變量的構造函數中,將步驟2中類A、B、C的CRuntimeClass類型的變量插入到鏈表的頭部。
這樣子,只要我們分別在類的定義和聲明中加入兩個宏DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC,並讓所有的類繼承自CObject,便可得到一個鏈表,該鏈表保存了各個類中CRuntimeClass類型的變量,鏈表頭部為CRuntimeClass::pFirstClass。
有了這個鏈表,我們便可以遍歷整個鏈表,通過
CRuntimeClass::CreateObject(lpszClassName)
方法根據類名lpszClassName來創建相應的對象了~
於是我們就在不修改已經做好的代碼的情況下,實現類對象的動態識別和生成的目的。以後,只要所有的類都派生自類CObject,並相應地加入兩個宏,就具有了動態識別和生成的能力。
(以上代碼比較簡略,不一定十分准確,意在直觀地理解、感受下這種實現的原理。)
通過學習,我們已經大致知道怎麼用C++實現動態生成類對象的功能了。下一篇我將此基礎之上,分析下本文開頭講到的數據Cache以及更新的相關功能代碼。