派生類的尷尬

類與類之間的繼承,是C++語言中經常談到的一種獲取父類中所有屬性的簡單方法。但在過去的幾個項目實施過程中,但卻發現實現起來並不是那麼方便,特別是對共享數據的初始化和c/s下面的傳輸(可能原因是基礎參數和派生的參數都在同一個數據表)。下面以金融軟件的期限爲例進行說明:
 
BaseTerm_T         : 基本期限參數,主要數據i_pterm
RfxForTerm_T      : 遠期期限參數(從BaseTerm_T派生),主要數據i_pforterm
RfxSwapTerm_T   : 掉期期限參數(從BaseTerm_T派生),主要數據i_pswapterm
 
派生類的尷尬在於:首先BaseTerm_T包含參數TermId, TermName等公有信息,而RfxForTerm_T和RfxSwapTerm_T有各自區別於對方的參數設置。因爲RfxForTerm_T和RfxSwapTerm_T都包含BaseTerm_T,爲了儘量減少數據量,在內存裏我們只需要保存一份BaseTerm_T的數據。於是,我們先初始化BaseTerm_T,再初始化RfxForTerm_T和RfxSwapTerm_T的參數,然後將後兩個類的公有數據部分指向到BaseTerm_T的數據部分。上面參數表遍歷了3次,爲避免遍歷3次我們還有另外一個辦法,就是在不在上述三個類的同一個函數裏面對三個類進行初始化,但這樣就破壞了類的封裝性。

C/S傳輸的時候,將三個類的數據分開傳輸,或按順序連接在一起,到接收端再重組參數數據(包括後兩個類的公有數據部分指向到BaseTerm_T的數據部分),傳輸的結構如下:

        BaseTerm_T數據段

          RfxForTerm_T數據段 

          RfxSwapTerm_T數據段
對於上面三個類的糅合,也可以另外定義一個類RfxTermMan_T進行處理,但有這種關係的派生類並不只這一組,還有BaseCurrency_T,BaseRate_T等等。有沒有一個統一的、封裝性更好的方法呢?最後發現下面也算是一種不會非常爛的方法吧。
1. 先定一個基類BaseObject_T,需要注意的是需通知派生類的幾個notify~必須爲virtual,不然派生類對該函數的重載將會被忽略:
//---------------------------------------------------------------------------
class BaseObject_T : TObject
{
protected:
    TList   *i_pextendlist;
    BaseObject_T *__fastcall getExtends(int nindex);
public:
    __fastcall BaseObject_T();
    __fastcall ~BaseObject_T();
 
    //參數父類調用時觸發事件,子類派生該類則被觸發事件  
    virtual void __fastcall notifyInitial(TQuery *pquery, int nstep, int nindex);
    virtual void __fastcall notifyGetLength(int &nlength, int nflag);
    virtual void __fastcall notifySetToMem(void *pmem, int nflag);
    virtual void __fastcall notifyGetFromMem(const void *pmem, int nflag);
    //註冊派生類,將派生類加入列表
    bool    __fastcall registerExtend(BaseObject_T *pextend);
    //解除派生類,將派生類從列表中刪除
    bool    __fastcall deregisterExtend(BaseObject_T *pextend);
    //C++ Builder支持的屬性
    __property BaseObject_T *Extends[int nindex] = { read = getExtends };
};
2. 其中notifyInitial等notify~開頭的函數實現就是調用列表中該類(派生類)的同名函數,如notifyInitial的實現方法:
//-----------------------------------------------------------------------------
void __fastcall BaseObject_T::notifyInitial(TQuery *pquery, int nstep, int nindex)
{
    int i;
    BaseObject_T *pextend;
    for (i = 0; i < i_pextendlist->Count; i++)
    {
        pextend = (BaseObject_T *)i_pextendlist->Items[i];
        pextend->notifyInitial(pquery, nstep, nindex);
    }
}
3. BaseTerm_T作爲父類從BaseObject_T派生,於是自動包含了BaseObject_T的各個函數:
class BaseTerm_T : public BaseObject_T {
4. RfxForTerm_T和RfxSwapTerm_T在構造函數中向Base_Term_T註冊,調用:
gp_baseterm->registerExtend(this);
析構函數中解除註冊,調用:
gp_baseterm->deregisterExtend(this);
4. BaseTerm_T在初始化的時候,
第一階段,獲取了參數的個數,並觸發所有的派生類進行初始化,nstep = 0;
            i_termcount = query -> RecordCount;
            ip_term = new Term_T[i_termcount];
            memset((char *)ip_term,'/0',sizeof(Term_T) * i_termcount);
            notifyInitial(query, 0, 0);
第二階段,遍歷所有參數記錄的時候,允許派生類初始化指定的某一個參數
            query -> First();
            for(i = 0; i < i_termcount && !query -> Eof; i++)
            {
                ip_term[i].termflag  = query -> FieldByName("IodFlag") -> AsInteger;
                notifyInitial(query, 1, i);
                query -> Next();
            }
5. RfxForTerm_T和RfxSwapTerm_T從BaseTerm_T派生,於是也有了BaseObject_T的一些函數,如notifyInitial,但是這兩個類是派生類,是被觸發的對象。這就是這種方法的關鍵了,父類在自身初始化以後(或者獲取參數長度、寫入內存,從內存讀取),通知子類也做同樣的操作。因此notifyInitial等的設計也應該根據需要而設定,並且感覺這個地方非常不靈活,懊惱。其中RfxForTerm_T::notifyInitial的設計如下:
//-----------------------------------------------------------------------------
void __fastcall RfxForTerm_T::notifyInitial(TQuery *pquery, int nstep, int nindex)
{
    if (nstep == 0)
    {
        if (i_pforterm != NULL)
            delete []i_pforterm;
        i_termcount = gp_baseterm->TermCount;
        i_pforterm = new RfxForTermItem_T[i_termcount];
        memset(i_pforterm, 0, sizeof(RfxForTermItem_T) * i_termcount);
        ip_term = gp_baseterm->BaseTerm;
    }
    else if (nstep == 1)
    {
        i_pforterm[nindex].valuedate  = 0;
        i_pforterm[nindex].spacedays  = 0;   
    }
}
6. 看看效果吧!
    gp_baseterm = new BaseTerm_T();
    gp_rfxforterm = new RfxForTerm_T();                       //同時向父類註冊
    gp_rfxswapterm = new RfxSwapTerm_T();                //同時向父類註冊
gp_baseterm -> initialization (DataModule1->TmpQuery);     //同時將調用子類代碼初始化
就上面的一段代碼,就已經將三個類都已經初始化過了
7. 備選方案:上面4說到在每一個派生類都要執行一次如下處理,
gp_baseterm->registerExtend(this);
gp_baseterm->deregisterExtend(this);
這樣似乎不是很方便。registerExtend註冊的只是BaseObject_T的指針,通過優化可以不在派生類進行處理,而改在父類BaseTerm_T進行處理。理由是派生類的構造函數同樣也需要調用父類的構造函數,析構函數也需要調用父類的,唯一需要注意的是,註冊的時候,不能把對①BaseTerm_T的初始化也註冊進來了,否則會導致在notifyInitial的時候,遞歸調用造成死循環。修改方法如下:
Ø         去除所有派生類的下面兩個函數調用
gp_baseterm->registerExtend(this);
gp_baseterm->deregisterExtend(this);
Ø         在BaseTerm_T的構造函數添加如下代碼
static int nrefcount = 0;                        //static變量
if (++nrefcount > 1)                              //第一次構造的肯定是父類
        gp_baseterm->registerExtend(this);
Ø         在BaseTerm_T的析構函數添加如下代碼
gp_baseterm->deregisterExtend(this);          //父類調用了也沒關係,因爲列表中沒有
 
 
至於c/s打包傳輸、解包的時候也可以按照上面一種思路進行處理。下面評估一下這種模型的優劣:
好處:
ü         類的封裝性比較好,對所有派生類統一處理,看起來就是爽
ü         類的客戶調用方便
ü         效率較高,當派生類和父類的數據在同一個表時,初始化只需要遍歷一次數據庫;當派生類和父類的數據不在同一個表時也能兼容處理。
劣處:
ü         編寫難度可能增加
ü         代碼也看起來沒那麼直觀
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章