2014年(6)
分类: C/C++
2014-08-26 15:19:24
假设有如下需求:接收到外系统的发送过来的报文,本系统进行来相应的解析与处理,
然后入库,随后...
经过我们的深思熟虑后便有了下面的设计:
业务基类:
class business
{
public:
business();
virtual ~business();
//暂用...代表其他可能存在的参数(不要理解成printf里面的...)
int query(char* buffer, ...);//输入参数 输出参数
int query(std::string& str, ...);
int save(char* buffer, ...); //输入参数
int save(std::string& str, ...);
int save(std::vector
};
business::business()
{
}
business::~business()
{
}
int business::query(char* buffer, ...)
{
(0).打印日志+判断参数有效性+转码之类的操作
(1).定义一个局部数据库对象或者从数据库连接池中获取到一个连接
(2).利用上述对象或连接调用相应的查询函数
(3).如果查询成功,给输出参数赋值
(4).如果采用了连接池的方式,将连接归还给池
(5).返回数据库操作成功或失败
}
int business::save(char* buffer, ...)
{
(0).打印日志+判断参数有效性+转码之类的操作
(1).定义一个局部数据库对象或者从数据库连接池中获取到一个连接
(2).利用上述对象或连接调用相应的保存(insert/update/merge)函数
(4).如果采用了连接池的方式,将连接归还给池
(5).返回数据库操作成功或失败
或者返回值不体现数据库操作成功与否的异步方式:
(0).打印日志+判断参数有效性+转码之类的操作
(1).将输入参数同步(...数据竞争,加锁解锁等)到ITC(线程间通讯)或者IPC(进程间通讯)数据中
ps:异步线程或进程采用定额+定时的机制,进行高效的批量处理
(2).函数返回
}
业务子类:
class businessA : public business
{
businessA();
~businessA();
//各种业务内容
...
{
query();
}
{
save();
}
};
class businessB : public business
{
businessB();
~businessB();
//各种业务内容
...
{
query();
}
{
save();
}
};
准备条件已完毕,代码运行流畅。
假如我们的代码需要移植到别的操作系统,比如unix到window,会不会有oracle到SQLsever
的转变呢?又或者在项目接近尾声的时候,客户(甲方)提出业务A的执行效率明显达不到所
需量,我们只好寻找更好的数据库封装方案,我们是全盘替换还是只针对业务A做修改(楼主
前几天就面临这种需求,因从事军工项目,现不方便具体言明具体事宜,之前都是使用浪潮公司
内存对齐方式的数据库查询方案,然发现项目吞吐量提高的一个瓶颈就在此,且其中一部分业
务就有明确一秒钟多少多少的效率要求,于是就找到了具有上佳表现的OTL,在全盘替换和两种
方案共存间盘旋很久.....),更或者...各种令人头疼的重构需求提到日程上的时候,才发现
上述设计是如此的没有弹性,如此的不堪一击。
数据库操作和业务之间的逻辑是继承"is-a"好还是组合"has-a"好?如果你使用继承定义了一个
类的行为,你就被这个行为困住,因为它是在编译期静态决定的,而且所有的子类都继承到相同
的行为,比如:上述方案对于永远不会使用到数据库操作的业务来说无疑是一种灾难性的设计。
(ps:不知这是否牵扯到抽象的层次或者禅的概念?)然而组合可以做到在运行时动态地扩展
对象的行为,组合不但能达到继承的行为的效果,还能设计出更好维护和更有弹性的代码。
对一条select/insert语句就提供一个连接还是针对一个业务里面的所有select/insert提供
一个连接,两者相对比便会发现:一个连接和一条语句绑定的话,即用即获取,用完便归还,
基本不会出现连接丢失,被篡改等恶意行为;一个连接和一个业务绑定的话,会节省若干获取
连接和释放连接的时间。
我们的修改(重构)原则是尽量少修改既有代码,同时又达到所需目的,因此我们想让那部分
有特殊效率需求的业务使用OTL,其他无特殊需求的暂使用既有inspur内存对齐方式,这其中可
能牵扯到的问题:
(1).otl和内存对齐方式数据库操作输入和输出参数不一致, 暂定使otl向原先的参数看齐(有点
像适配器模式),输入参数方面otl放弃otl_stream重载符<<的形式,采用otl_cursor::direct_exec
静态方式,即传入组织好的select语句,输出参数方面otl将既有的otl_stream重载>>重载符
拼成内存对齐的方式
(2).可能需要实现两个连接池,因其初始化等各种方式必不一致,并提供全局访问点
(3).不想或者怕引发bug不修改既有代码,然后续代码仍有必要继续使用原先的inspur内存对齐方式或者
OTL伪装的内存对齐方式吗?仔细思量直接使用OTL的otl_stream必然是上上选。如何提供访问点?
使用OTL自己的方法/函数?这些方式放在抽象出来的数据库动作基类的话,原先的内存对齐
方式却没法实现,放在OTL自己的数据库动作子类中的话也不是很好.....因为还有更简单且更好的方式,
就是将采用组合方式的成员变量使用protected修饰而不是使用private修饰。
(4).之前讨论的那个继承面临的灾难点:不需要数据操作的类怎么办?抽象出来的数据库动作基类
提供缺省的空操作?还是提供一个空的对象子类?选择后者,提供缺省空操作的话(虚函数,非纯
虚函数),子类可能不会去实现自己的方式,定义成纯虚函数的话,强制子类的实现,但没有规定
子类实现中有无内容。
(5).假如以后有全部修改成OTL的需求,要么全部使用OTL伪装的形式,要么全部使用otl_stream的
形式,大型重构请慎重,提前评估,须经大量测试。
展现oo设计的时间来临:
将原有inspur内存对齐方式和OTL方式数据库动作抽象出一个基类,要求他们必须实现那些方法,
class dbase
{
public:
dbase(){}
virtual ~dbase(){}
//暂用...代表其他可能存在的参数(不要理解成printf里面的...)
int query(char* buffer, ...) = 0;//输入参数 输出参数
int query(std::string& str, ...) = 0;
int save(char* buffer, ...) = 0; //输入参数
int save(std::string& str, ...) = 0;
int save(std::vector
...
};
实现inspur内存对齐方式数据库动作类:
class inspur_dbase : public dbase
{
inspur_dbase()
:inspur_ptr(NULL)
{
get_conn();
}
~inspur_dbase()
{
release_conn();
}
int query(char* buffer; ...)
{
return inspur_ptr->query();
}
int save(char* buffer; ...)
{
return inspur_ptr->save();
}
...
private:
/*
没有将获取连接和释放连接提炼出来放在rm_dbase中声明成虚函数是有原因的:
绝对不能在(基类de)构造函数和析构函数中调用虚函数
同时注意获取连接和释放连接用private进行了修饰
*/
int get_conn()
{
//从池全局访问点中获取连接
return inspur_database_pool.get(inspur_ptr);//.或者->
}
int release_conn()
{
//从池全局访问点中释放连接
return inspur_database_pool.release(inspur_ptr);//.或者->
}
private:
inspur_database* inspur_ptr;//inspur内存对齐方式数据库类指针
};
实现OTL方式数据库动作类:
class otl_dbase : public dbase
{
otl_dbase()
:otl_ptr(NULL)
{
get_conn();
}
~otl_dbase()
{
release_conn();
}
int query(char* buffer; ...)
{
return otl_ptr->query();
}
int save(char* buffer; ...)
{
return otl_ptr->save();
}
...
private:
/*
没有将获取连接和释放连接提炼出来放在rm_dbase中声明成虚函数是有原因的:
绝对不能在(基类de)构造函数和析构函数中调用虚函数
同时注意获取连接和释放连接用private进行了修饰
*/
int get_conn()
{
//从池全局访问点中获取连接
return otl_database_pool.get(otl_ptr);//.或者->
}
int release_conn()
{
//从池全局访问点中释放连接
return otl_database_pool.release(otl_ptr);//.或者->
}
private:
otl_database* otl_ptr;//OTL方式数据库类指针
};
实现空对象类:
class null_dbase : public dbase
{
null_dbase(){}
~null_dbase(){}
int query(char* buffer; ...)
{
return 0;
}
int save(char* buffer; ...)
{
return 0;
}
...
}
业务基类的修改(采用组合形式):
class business
{
public:
business(const std::string& type);
virtual ~business();
//暂用...代表其他可能存在的参数(不要理解成printf里面的...)
int query(char* buffer, ...);//输入参数 输出参数
int query(std::string& str, ...);
int save(char* buffer, ...); //输入参数
int save(std::string& str, ...);
int save(std::vector
//private:
protected:
std::auto_ptr
};
business::business(const std::string& type)
:dbase_ptr(NULL)
{
if(type == "inspur")
dbase_ptr.reset(new inspur_dbase);
else if(type == "otl")
dbase_ptr.reset(new otl_dbase);
else if(type == "null")
dbase_ptr.reset(new null_dbase);
}
business::~business()
{
}
将原先的具体实现修改如下:
int business::query(char* buffer,...)
{
dbase_ptr->query();
}
int business::save(char* buffer, ...)
{
dbase_ptr->query();
}
业务子类的修改:
class businessA : public business
{
businessA()
:business(std::string("otl"))
{
}
~businessA()
{
}
//各种业务内容,和之前一样
...
};
class businessB : public business
{
businessB()
: business(std::string("inspur"))
{
}
~businessB()
{
}
//各种业务内容,和之前一样
...
};
以上便是策略模式的使用。
最后引用《head first 设计模式(中文版)》中的策略模式的定义:
定义算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于
使用算法的客户。
附:
//规定最大对齐长度
#pragma pack(8)
// 从数据库中查询数据
int rm_otl_dbase::query_from_database(const char* buffer, int len, int data_size, char** data, int* data_len)
{
LOG_TRACE("ENTER INTO rm_otl_dbase::query_from_database!");
try
{
otl_stream otl;
otl_conn->get_otl_conn().auto_commit_off();
otl.open(1, buffer, otl_conn->get_otl_conn());
otl.flush();
std::queue
otl_column_desc* desc;
int desc_len = 0;
int nCount = 0;
desc = otl.describe_select(desc_len);
while(!otl.eof())
{
char* it = (char*)calloc(1, data_size);
assert(it);
int nOffset = 0;
for(int i=0;i
if(desc[i].dbtype == 1)
{
string id;
otl >> id;
if(0 != int(id.size()))
memcpy(it+nOffset, id.c_str(), desc[i].dbsize);
nOffset = nOffset + desc[i].dbsize + 1;
}
else if(desc[i].dbtype == 2)
{
if(0 == desc[i].scale)
{
int id;
otl >> id;
if(0 != nOffset%sizeof(int))
{
nOffset = nOffset + sizeof(int) - nOffset%sizeof(int);
}
memcpy(it+nOffset, &id, sizeof(int));
nOffset = nOffset + sizeof(int);
}
else
{
double id;
otl >> id;
if(0 != nOffset%sizeof(double))
{
nOffset = nOffset + sizeof(double) - nOffset%sizeof(double);
}
memcpy(it+nOffset, &id, sizeof(double));
nOffset = nOffset + sizeof(double);
}
}
else
{
nOffset += desc[i].dbsize;
}
}
otl.check_end_of_row();
__queue.push(it);
++nCount;
}
int nRet = 0;
if(0 == nCount)
{
*data = NULL;
*data_len = 0;
// 从数据库查询结果无内容
LOG_INFO("buffer:"<< buffer <<" database has nothing(nLen = 0 or data = NULL)!");
nRet = 1;
}
else
{
int nLength = 0;
*data_len = nCount*data_size;
*data = (char*)calloc(1, *data_len);
assert(*data);
while(!__queue.empty())
{
char* it = __queue.front();
memcpy(*data+nLength, it, data_size);
__queue.pop();
free(it);
nLength += data_size;
}
LOG_INFO("ret: 0"<< " sql:" << buffer);
nRet = 0;
}
otl.close();
return nRet;
}
catch(otl_exception& oe)
{
cout << "otl_exception" << endl;
cout << "oe.msg:" << oe.msg << endl;
}
catch(exception& ex)
{
cout << "exception" << endl;
}
catch(...)
{
;
}
*data = NULL;
*data_len = 0;
LOG_INFO("rm_otl_dbase::query_from_database fail!");
LOG_TRACE("LEAVE rm_otl_dbase::query_from_database!");
return -1;
}