不要重复发明轮子:C++重用的5重境界

软件领域有一个著名的描述软件重用的谚语:不要重复发明轮子

这个道理是很简单,也很明白的,谁都不想重复无用的劳动,但具体实践中我们该如何避免重复发明轮子呢?

各位注意了,谚语中是说“重复发明”,不是说“重复使用”,也就是说我们实践中其实也是避免不了重复使用轮子的,因此实践中我们的对策也可以用一句简单的语句表达:发明能够重复使用的轮子

下面我们就以C++语言为例,看看究竟如何“发明重复使用的轮子”。

 

第一重境界:代码重用

最简单的当然就是代码重用了:写一段公共代码,然后放到各个项目里面去编译。

这种方式最直观,但如果你真的在实践中如此应用,那么将面临如下问题(假设3个项目共用):

1)一份代码,三份拷贝

因为采用的是代码编译,所以一份代码在存储时会占用3份的磁盘空间,在运行时会占用3份的内存空间。

当然在现在这个磁盘空间动不动上100G,内存动不动上G的年代,可能大家对这种浪费不以为然,但是回过头去看看历史,如果在你的内存只有32M的年代,这种浪费就不一般了,将会对系统产生很大影响。

2)一次修改,三次编译

假如某一天这份代码修改了,也许是一个小小的BUG,也许是一点小小的优化,但最终的结果都是一样的:所有用到这个公共代码的项目都需要重新编译。

对于普通的小程序来说,编译可能是几秒到几分钟的事情,功能验证也很简单,编译问题看起来还不是很严重;但如果是企业级、电信级的程序,这种编译加验证的工作量是巨大的,而且要部署到已经运行的系统中时,可能需要卸载安装。

 

第二重境界:简单DLL

稍有经验的人都知道,要解决代码重用的问题其实已经有简单的方法了,那就是动态链接库(Windows平台是DLLLinux/UNIXso,下面以DLL为例说明)。

简单的DLL实现如下:将对象、方法的定义放在DLL里面,使用时只需要包含DLL的头文件即可。

这样简单的一个设计,就能够解决代码重用的一个大问题:一份DLL只占一份磁盘空间、一份内存空间

但为什么我没有说解决了另外一个大问题——编译的问题呢?

乍一看好像是解决了编译的问题,例如我修改函数体内的某个执行语句,或者加一个逻辑判断,只需要编译DLL就可以了呀!

如果只修改函数体那当然是没有问题,但关键是世界没有那么完美和简单,我们可以做很多的修改,例如:

1)  修改类定义:例如增加一个成员变量。


2)  修改函数定义:例如增加一个函数参数,修改某个入参类型;

3)  修改函数体:例如增加一个语句,一个调用等。

以上修改除了第三种修改只需要编译DLL外,其它两种修改都需要重新编译整个项目,也就是说,DLL能够解决空间和编译的部分问题,不能解决所有的编译问题。

革命尚未成功,同志还需努力!!

第三重境界:代理接口DLL

看到这个名字,可能大家有点迷糊:代理?接口?DLL?三个风马牛不相及的东东扯到一块是什么意思呢?

其实只要按照字面意思就能够大概理解:

代理:就是设计模式中的代理模式;

接口:就是Java中的Interface一个概念;

DLL:就是动态链接库了:)

翻译成一句完整的话就是:DLL通过代理模式对外提供接口

 

下面我们看看这个“代理接口DLL”是如何实现的。

/*******************************DLL代码*********************************/

//声明部分

class __decspec(dllexport) InterfaceClass{    //声明接口类

    class RealizeClass;                   //引入实现类

    RealizeClass* m_pRealizeClass;        //指向实现类的指针,咦,怎么会有数据

    public:

    void Function1(int param1, char param2 );

    void Function2(int param1;

    void Function3(bool param1, char param2 );

    ……………………………………………….

}

 

//实现部分

InterfaceClass::InterfaceClass(){

    m_pRealizeClass = new RealizeClass();

}

 

InterfaceClass:: Function1(int param1, char param2 ){

    return m_pRealizeClass-> Function1(param1, param2 );

}

//其它函数略。

第四重境界:继承接口DLL

看起来“代理接口DLL”已经能够很好的完成任务了,但追求完美的你是否总觉得有的地方不够优美呢?

关键就在于这部分:

InterfaceClass:: Function1(int param1, char param2 ){

    return m_pRealizeClass-> Function1(param1, param2 );

}

以上这段代码是代理模式的一种实现方法,但也有它的不足之处:对于RealizeClass的函数,InterfaceClass都要写一个函数,每个函数的写法都是一样的:

return m_pRealizeClass-> FunctionXXX(param1, param2 ……………..);

对于只有几个方法的类来说,这可能没有什么,但是如果RealizeClass类有几十上百个方法,那InterfaceClass就有几十上百个这样类似的函数,看起来是不是很晕呢?

 

有没有一种方法能够不用写这么多的无聊的函数呢?有,这就是本章要介绍的“继承接口DLL”。我们还是按照第三重境界的方法来解释这个方法:

继承:就是面向对象的继承概念

接口:就是Java中的Interface一个概念;

DLL:就是动态链接库了:)

翻译成一句话就是:DLL通过继承的方法对外提供接口

 

如果你还记得第三重境界的实现方式,一对比就会发现,这两个方法其实大同小异,关键就是具体的实现方式不一样:一个通过代理模式,一个通过继承方式。那么我们就来看看这个继承方式具体如何实现。

/*******************************DLL代码声明部分开始**********************/

class InterfaceClass{    //声明接口类,无成员数据,只有方法,这里不用dllexport声明,//为什么呢,请自行查阅相关资料?

    public:

    void Function1(int param1, char param2 ) = 0 //声明为纯虚函数,子类必须改写;

    void Function2(int param1 = 0 ;

    void Function3(bool param1, char param2 ) = 0 ;

}

 

class RealizeClass::public InterfaceClass{ //继承接口类,函数必须改写

       //成员变量

       …………………………………..

       //继承的函数,需要重写。

    public:

    void Function1(int param1, char param2 );

    void Function2(int param1 );

    void Function3(bool param1, char param2 ) ;

}

 

//这两个函数是“继承接口DLL”实现关键,后面介绍为什么。

extern InterfaceClass* g_InterfaceClassPtr ; //不要和下面的extern混淆哈:)

extern “C” InterfaceClass* __decspec(dllexport)  CreateInterfaceClass();

extern “C” InterfaceClass* __decspec(dllexport)  DeleteInterfaceClass();

/******************************* DLL代码声明部分结束**********************/

 

/*******************************DLL代码定义部分开始**********************/

void RealizeClass::Function1(){

       //函数具体实现,do what you want!!!

    ……………………………..

}

 

void RealizeClass::Function2(){

       //函数具体实现,do what you want!!!

    ……………………………..

}

 

void RealizeClass::Function3(){

       //函数具体实现,do what you want!!!

    ……………………………..

}

 

InterfaceClass* g_InterfaceClassPtr = NULL;

 

InterfaceClass* CreateInterfaceClass(){

   if(g_InterfaceClassPtr == NULL){

       g_InterfaceClassPtr = new RealizeClass(); //生成的是具体的类

}

 

return g_InterfaceClassPtr;

}

 

InterfaceClass* DeleteInterfaceClass(){

     delete g_InterfaceClassPtr;

     g_InterfaceClassPtr = NULL;

}

 

/*******************************DLL代码定义部分结束**********************/

 

 

/***************************使用DLL的客户端代码********************/

InterfaceClass* pInterfaceClass = CreateInterfaceClass();

pInterfaceClass->Function1(param1, param2);

………………………………………………………

DeleteInterfaceClass();

/***************************使用DLL的客户端代码********************/

 

样例代码到这里就结束了,我们来总结一下这种方法的关键实现点:

1)实现类继承接口类,而不是“代理接口DLL”中的接口类包含实现类的指针(UML中的聚合Aggregation的一种实现方式);

2)由于第一条的存在,使得客户端不能直接new某个对象,所以要通过CreateInterfaceClass来创建一个具体的实现类。

3)由于第二条创建了实现类,为了避免内存泄漏,所以要DeleteInterfaceClass

 


/*******************************DLL代码*********************************/

 

/***************************使用DLL的客户端代码********************/

InterfaceClass   pInterfaceClass = new InterfaceClass();

pInterfaceClass->Function1(param1, param2);

/***************************使用DLL的客户端代码********************/

各位看完上面的样例,基本上应该都能够明白是如何实现的,但可能会问“为什么还是有一个指针数据类型呢”?不是说没成员数据的吗?

是的,这里关键就在于这个指针,虽然有这个成员数据,但是大家想一想,指针是一个固定大小的类型,而且客户端程序是看不到这个指针的。因此不管对于以下哪个变化,InterfaceClass的结构都不变化,客户端的代码也不受任何影响,不需要重新编译。

1)  具体实现的RealizeClass增加、修改、删除成员数据;

2)  RealizeClass有一天改了名称变成了RealizeClassSE

3)  RealizeClassFunction1函数改名了,甚至加了一个缺省参数了。

 

讲了半天,基本上把“代理接口DLL”是一个什么东东、如何实现讲完了,但是最根本的问题还没有回答——这重境界要解决什么问题?

其实看完如何实现后,聪明的你基本上都能猜出要解决什么问题了,当然就是第二重境界遗留的两个问题了:

1)  修改类定义:例如增加一个成员变量。

2)  修改函数定义:例如增加一个函数参数,修改某个入参类型;

代理接口DLL通过代理模式(其实本质上就是一个指针)解决了上述两个问题,把对外呈现和内部实现分别由不同的类实现,然后通过一个简单的指针将两个类连接起来

第五重境界:消息通信

话说当年明教教主在连乾坤大挪移的时候,实际上并没有所谓的第7重,这第7重只是创始人凭借着自己的聪明才智想出来的,根本无法证实是否正确,幸好张无忌没有练才躲过一劫。

其实我们这里的所谓第5重也是我凭空想出来的:)大家接下来也可以看到,这一重境界其实和C++或者DLL完全没有关系,但这一重境界绝对不是凭空乱想,而且也绝对不是无法证实的,这一重境界是每个IT人都知道的,也许是每个人进入IT界接触的第一个重用方法——消息通信

aha,是不是觉得很简单、很普通、很傻很天真?!!

但是仔细想想,这确实是最高的重用境界,我们将这种方法与DLL方法来比较一下:

1)消息通信和编译无关,DLL和编译相关;

2)消息通信支持分布式部署,DLL不支持分布式部署;

3)消息通信和具体语言无关,C++的程序可以和Java的程序通信;

4)消息通信可以和操作系统无关,DLL和操作系统绑定的;

看起来消息通信这种方式几乎完美了,那我们还要DLL干嘛呢?前面讲了那么多,那不是浪费口水和时间?

当然不是了,消息通信也存在缺点的:

1)要通过某种方式来收发消息,例如TCPSCTPTDM链路;

2)要制定协议来规定收发消息规则和行为规则;

3)要对发送消息和接受消息进行编解码;

 

总结起来就是消息通信是重量级的,DLL是轻量级的。

 

废话说了这么多,我们举一个简单的样例,由于消息通信实现比较复杂,这里就不写代码了,简单的描述一下。

例如系统有ABCD 4个模块,都需要访问数据库,对数据库进行操作,由于对数据库的操作基本上建立连接、执行操作、释放连接,这些操作基本上都是一样的。

如果是DLL实现方式,那么就把建立连接、执行操作、释放连接做成DLL,然后由每个模块去调用DLL的对应函数。

如果是消息通信,按照如下机制实现一个消息通信:

1)新建一个模块E,这个模块完成建立连接、执行操作、释放操作的功能;

2)规定ABCD通过TCP/IPE通信;

3)规定消息格式,例如采用TLV编码,或者二进制编码等

4)规定消息内容,例如:发1标识建立连接、100表示建立连接结果,2表示释放连接,200表示释放连接的结果,等等

 

例子到这里就结束了,是不是觉得很简单,或者意犹未尽?

是的,一旦采用消息通信方式,你可以发挥的余地就很大了,还是上面那个例子,我们可以做很多的优化,例如:

1ABCD不再需要关注建立连接和释放连接了,只需要关注数据操作就ok了;

2E模块可以采用连接池、多线程等技术来提高性能;

3)如果底层数据库修改了,只需要修改E就可以了,ABCD完全不需要任何修改,其实ABCD都不知道底层数据库是Oracle还是DB2.

4E可以用任何编程语言编写,也可以运行在任何操作系统上;

5E进程可以实现双机主备等机制来保证可靠性或者性能;

……………………………………………………

 

总结

好不容易把这个东东讲完了,也算是自己总结归纳了一下,当然,由于才疏学浅,难免出现遗漏和错误,还请大家纠正。

由于篇幅有限,每一篇都写得比较简单,基本上就是把设计思想介绍了一下,实际中应用肯定还有很多问题和细节需要大家去解决,在这里就不一一细讲了(例如第4重境界需要解决多线程的同步问题、第5重境界需要设计好消息格式和消息内容等)。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值