不要重复发明轮子:C++重用的5重境界
軟件領域有一個著名的描述軟件重用的諺語:不要重復發明輪子!
這個道理是很簡單,也很明白的,誰都不想重復無用的勞動,但具體實踐中我們該如何避免重復發明輪子呢?
各位注意了,諺語中是說“重復發明”,不是說“重復使用”,也就是說我們實踐中其實也是避免不了重復使用輪子的,因此實踐中我們的對策也可以用一句簡單的語句表達:發明能夠重復使用的輪子!
下面我們就以C++語言為例,看看究竟如何“發明重復使用的輪子”。
?
第一重境界:代碼重用
最簡單的當然就是代碼重用了:寫一段公共代碼,然后放到各個項目里面去編譯。
這種方式最直觀,但如果你真的在實踐中如此應用,那么將面臨如下問題(假設3個項目共用):
1)一份代碼,三份拷貝
因為采用的是代碼編譯,所以一份代碼在存儲時會占用3份的磁盤空間,在運行時會占用3份的內存空間。
當然在現在這個磁盤空間動不動上100G,內存動不動上G的年代,可能大家對這種浪費不以為然,但是回過頭去看看歷史,如果在你的內存只有32M的年代,這種浪費就不一般了,將會對系統產生很大影響。
2)一次修改,三次編譯
假如某一天這份代碼修改了,也許是一個小小的BUG,也許是一點小小的優化,但最終的結果都是一樣的:所有用到這個公共代碼的項目都需要重新編譯。
對于普通的小程序來說,編譯可能是幾秒到幾分鐘的事情,功能驗證也很簡單,編譯問題看起來還不是很嚴重;但如果是企業級、電信級的程序,這種編譯加驗證的工作量是巨大的,而且要部署到已經運行的系統中時,可能需要卸載安裝。
?
第二重境界:簡單DLL
稍有經驗的人都知道,要解決代碼重用的問題其實已經有簡單的方法了,那就是動態鏈接庫(Windows平臺是DLL,Linux/UNIX是so,下面以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)??RealizeClass的Function1函數改名了,甚至加了一個缺省參數了。
?
講了半天,基本上把“代理接口DLL”是一個什么東東、如何實現講完了,但是最根本的問題還沒有回答——這重境界要解決什么問題?
其實看完如何實現后,聰明的你基本上都能猜出要解決什么問題了,當然就是第二重境界遺留的兩個問題了:
1)??修改類定義:例如增加一個成員變量。
2)??修改函數定義:例如增加一個函數參數,修改某個入參類型;
代理接口DLL通過代理模式(其實本質上就是一個指針)解決了上述兩個問題,把對外呈現和內部實現分別由不同的類實現,然后通過一個簡單的指針將兩個類連接起來
第五重境界:消息通信
話說當年明教教主在連乾坤大挪移的時候,實際上并沒有所謂的第7重,這第7重只是創始人憑借著自己的聰明才智想出來的,根本無法證實是否正確,幸好張無忌沒有練才躲過一劫。
其實我們這里的所謂第5重也是我憑空想出來的:)大家接下來也可以看到,這一重境界其實和C++或者DLL完全沒有關系,但這一重境界絕對不是憑空亂想,而且也絕對不是無法證實的,這一重境界是每個IT人都知道的,也許是每個人進入IT界接觸的第一個重用方法——消息通信。
aha,是不是覺得很簡單、很普通、很傻很天真?!!
但是仔細想想,這確實是最高的重用境界,我們將這種方法與DLL方法來比較一下:
1)消息通信和編譯無關,DLL和編譯相關;
2)消息通信支持分布式部署,DLL不支持分布式部署;
3)消息通信和具體語言無關,C++的程序可以和Java的程序通信;
4)消息通信可以和操作系統無關,DLL和操作系統綁定的;
看起來消息通信這種方式幾乎完美了,那我們還要DLL干嘛呢?前面講了那么多,那不是浪費口水和時間?
當然不是了,消息通信也存在缺點的:
1)要通過某種方式來收發消息,例如TCP、SCTP、TDM鏈路;
2)要制定協議來規定收發消息規則和行為規則;
3)要對發送消息和接受消息進行編解碼;
?
總結起來就是消息通信是重量級的,DLL是輕量級的。
?
廢話說了這么多,我們舉一個簡單的樣例,由于消息通信實現比較復雜,這里就不寫代碼了,簡單的描述一下。
例如系統有A、B、C、D 4個模塊,都需要訪問數據庫,對數據庫進行操作,由于對數據庫的操作基本上建立連接、執行操作、釋放連接,這些操作基本上都是一樣的。
如果是DLL實現方式,那么就把建立連接、執行操作、釋放連接做成DLL,然后由每個模塊去調用DLL的對應函數。
如果是消息通信,按照如下機制實現一個消息通信:
1)新建一個模塊E,這個模塊完成建立連接、執行操作、釋放操作的功能;
2)規定A、B、C、D通過TCP/IP與E通信;
3)規定消息格式,例如采用TLV編碼,或者二進制編碼等
4)規定消息內容,例如:發1標識建立連接、100表示建立連接結果,2表示釋放連接,200表示釋放連接的結果,等等
?
例子到這里就結束了,是不是覺得很簡單,或者意猶未盡?
是的,一旦采用消息通信方式,你可以發揮的余地就很大了,還是上面那個例子,我們可以做很多的優化,例如:
1)A、B、C、D不再需要關注建立連接和釋放連接了,只需要關注數據操作就ok了;
2)E模塊可以采用連接池、多線程等技術來提高性能;
3)如果底層數據庫修改了,只需要修改E就可以了,A、B、C、D完全不需要任何修改,其實ABCD都不知道底層數據庫是Oracle還是DB2.
4)E可以用任何編程語言編寫,也可以運行在任何操作系統上;
5)E進程可以實現雙機主備等機制來保證可靠性或者性能;
……………………………………………………
?
總結
好不容易把這個東東講完了,也算是自己總結歸納了一下,當然,由于才疏學淺,難免出現遺漏和錯誤,還請大家糾正。
由于篇幅有限,每一篇都寫得比較簡單,基本上就是把設計思想介紹了一下,實際中應用肯定還有很多問題和細節需要大家去解決,在這里就不一一細講了(例如第4重境界需要解決多線程的同步問題、第5重境界需要設計好消息格式和消息內容等)。
總結
以上是生活随笔為你收集整理的不要重复发明轮子:C++重用的5重境界的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 详解 Python 源码之对象机制
- 下一篇: s3c2440移植MQTT