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