派生类的尴尬

博客介绍了C/S传输时,将三个类的数据分开传输或按顺序连接,在接收端重组参数数据,包括让后两个类的公有数据部分指向BaseTerm_T的数据部分,还给出了传输结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类与类之间的继承,是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打包传输、解包的时候也可以按照上面一种思路进行处理。下面评估一下这种模型的优劣:
好处:
ü         类的封装性比较好,对所有派生类统一处理,看起来就是爽
ü         类的客户调用方便
ü         效率较高,当派生类和父类的数据在同一个表时,初始化只需要遍历一次数据库;当派生类和父类的数据不在同一个表时也能兼容处理。
劣处:
ü         编写难度可能增加
ü         代码也看起来没那么直观
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值