本文首先提出平臺(tái)相關(guān)代碼造成的兩個(gè)問題,然后針對(duì)這兩個(gè)問題循序漸進(jìn)依次提出解決方案,在分析了前兩個(gè)方案弱點(diǎn)的基礎(chǔ)上,最后著重介紹一種基于多種設(shè)計(jì)模式的 Linux 平臺(tái)相關(guān)代碼的解決方案,并給出此方案的 C++ 實(shí)現(xiàn)。
Linux 平臺(tái)相關(guān)代碼帶來的問題
目前市場(chǎng)上存在著許多不同的 Linux 平臺(tái)(例如:RedHat, Ubuntu, Suse 等),各大廠商和社區(qū)都在針對(duì)自己支持的平臺(tái)進(jìn)行優(yōu)化,為使用者帶來諸多方便的同時(shí)也對(duì)軟件研發(fā)人員在進(jìn)行編碼時(shí)帶來不少問題:
由于程序中不可避免的存在平臺(tái)相關(guān)代碼(系統(tǒng)調(diào)用等),軟件研發(fā)人員為了保證自己的產(chǎn)品在各個(gè) Linux 平臺(tái)上運(yùn)行順暢,一般都需要在源代碼中大量使用預(yù)編譯參數(shù),這樣會(huì)大大降低程序的可讀性和可維護(hù)性。
接口平臺(tái)無關(guān)性的原則是研發(fā)人員必須遵循的準(zhǔn)則。但是在處理平臺(tái)相關(guān)代碼時(shí)如果處理不當(dāng),此原則很有可能被破壞,導(dǎo)致不良的編碼風(fēng)格,影響代碼的擴(kuò)展和維護(hù)。
本文將針對(duì)這兩個(gè)問題循序漸進(jìn)依次提出解決方案。
通過設(shè)置預(yù)編譯選項(xiàng)來處理平臺(tái)相關(guān)代碼
通過為每個(gè)平臺(tái)設(shè)置相關(guān)的預(yù)編譯宏能夠解決 Linux 平臺(tái)相關(guān)代碼的問題,實(shí)際情況下,很多軟件開發(fā)人員也樂于單獨(dú)使用這種方法來解決問題。
假設(shè)現(xiàn)有一動(dòng)態(tài)庫 Results.so,SomeFunction() 是該庫的一個(gè)導(dǎo)出函數(shù),該庫同時(shí)為 Rhel,Suse,Ubuntu 等三個(gè)平臺(tái)的 Linux 上層程序服務(wù)。(后文例子均基于此例并予以擴(kuò)展。)
清單 1. 設(shè)置預(yù)編譯選項(xiàng)示例代碼如下:
// Procedure.cpp void SomeFunction() { //Common code for all linux ...... ...... #ifdef RHEL SpecialCaseForRHEL(); #endif #ifdef SUSE SpecialCaseForSUSE(); #endif #ifdef UBUNTU SpecialCaseForUBUNTU(); #endif //Common code for all linux ...... ...... #ifdef RHEL SpecialCase2ForRHEL(); #endif #ifdef SUSE SpecialCase2ForSUSE(); #endif #ifdef UBUNTU SpecialCase2ForUBUNTU(); #endif //Common code for all linux ...... ...... } IT網(wǎng),http://www.it.net.cn
開發(fā)人員可以通過設(shè)置 makefile 宏參數(shù)或者直接設(shè)置 gcc 參數(shù)來控制實(shí)際編譯內(nèi)容。?IT網(wǎng),http://www.it.net.cn
例如:
gcc -D RHEL Procedure.cpp -o Result.so -lstdc++ // Use RHEL marco
SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU() 分別在該庫 (Results.so) 的其他文件中予以實(shí)現(xiàn)。
圖 1. 清單 1 代碼的結(jié)構(gòu)圖
帶來的問題
SomeFunction() 函數(shù)代碼冗余,格式混亂。本例僅涉及三個(gè)預(yù)編譯選項(xiàng),但實(shí)際情況中由于 Linux 版本眾多并且可能涉及操作系統(tǒng)位數(shù)的問題,增加對(duì)新系統(tǒng)的支持會(huì)導(dǎo)致預(yù)編譯選項(xiàng)不斷增多,造成 SomeFunction() 函數(shù)結(jié)構(gòu)十分混亂。
新增其他平臺(tái)相關(guān)接口(例如:增加 SpecialCase3ForRHEL(),SpecialCase3ForSUSE(),SpecialCase3ForUBUNTU),會(huì)成倍增加代碼中預(yù)編譯宏的數(shù)量。
破壞了接口平臺(tái)無關(guān)性的原則。SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU() 只是同一功能各個(gè)平臺(tái)的不同實(shí)現(xiàn),屬于封裝內(nèi)容,不應(yīng)該分開暴露給調(diào)用者。
可見,簡(jiǎn)單利用預(yù)編譯宏來解決平臺(tái)相關(guān)代碼產(chǎn)生的問題不是一個(gè)好的方法,并沒有解決本文開始提出的兩個(gè)問題。后文將通過三個(gè)方案依次解決這些問題。
解決方案 1:根據(jù)接口平臺(tái)無關(guān)性原則進(jìn)行優(yōu)化
實(shí)質(zhì)上,SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU() 只是同一功能在不同平臺(tái)上的實(shí)現(xiàn),SpecialCase2ForRHEL(),SpecialCase2ForSUSE(),SpecialCase2ForUBUNTU() 亦如此。對(duì)于調(diào)用者,應(yīng)該遵循接口平臺(tái)無關(guān)性的原則,使用統(tǒng)一的接口進(jìn)行調(diào)用,這樣才能簡(jiǎn)化代碼,使代碼易于維護(hù)。
清單 2. 解決方案 1 示例代碼如下:
// Procedure.cpp void SomeFunction() { //Common code for all linux ...... ...... SpecialCase(); //Common code for all linux ...... ...... SpecialCase2(); //Common code for all linux ...... ...... } void SpecialCase() { //Common code for all linux ...... ...... #ifdef RHEL SpecialCaseForRHEL(); #endif #ifdef SUSE SpecialCaseForSUSE(); #endif #ifdef UBUNTU SpecialCaseForUBUNTU(); #endif //Common code for all linux ...... ...... } void Special2Case() { //Common code for all linux ...... ...... #ifdef RHEL SpecialCase2ForRHEL(); #endif #ifdef SUSE SpecialCase2ForSUSE(); #endif #ifdef UBUNTU SpecialCase2ForUBUNTU(); #endif //Common code for all linux ...... ...... }
此方案的優(yōu)點(diǎn):
遵循了接口平臺(tái)無關(guān)性原則,同樣的功能只提供一個(gè)接口,每個(gè)平臺(tái)的實(shí)現(xiàn)屬于實(shí)現(xiàn)細(xì)節(jié),封裝在接口內(nèi)部。此方案提供了一定的封裝性,簡(jiǎn)化了調(diào)用者的操作。
此方案的缺點(diǎn):
預(yù)編譯宏泛濫的問題仍然沒有解決,每次新增功能函數(shù),就會(huì)成倍增加預(yù)編譯宏的數(shù)量。同樣每次增加對(duì)已有功能新平臺(tái)的支持,也會(huì)不斷增加預(yù)編譯宏的數(shù)量。
可見,此方案部分解決了本文開始提出的兩個(gè)問題中的一個(gè),但仍有問題需要繼續(xù)解決。
解決方案 2: 通過分層對(duì)進(jìn)行優(yōu)化
換一個(gè)角度來思考,可以在二進(jìn)制層面對(duì)平臺(tái)相關(guān)代碼進(jìn)行優(yōu)化。通過對(duì)庫的結(jié)構(gòu)進(jìn)行分層來優(yōu)化,為每個(gè) Linux 平臺(tái)提供單獨(dú)的實(shí)現(xiàn)庫,并且把調(diào)用端獨(dú)立提取出來。如下圖所示:
圖 2: 方案 2 的結(jié)構(gòu)圖
此方案單獨(dú)將調(diào)用端抽象出來,將每個(gè)平臺(tái)實(shí)現(xiàn)端的相關(guān)代碼提取出來做成一個(gè)單獨(dú)的庫(Rhel.so,Suse.so,Ubuntu.so)。SpecialCase() 為同一功能在不同平臺(tái)的實(shí)現(xiàn),采用相同接口名。底層庫需要與 Results.so 庫同時(shí)發(fā)布,也就是說,Redhat 版本發(fā)布時(shí)需同時(shí)打包 Results.so 和 Rhel.so,其他版本亦然。?Linux學(xué)習(xí),http:// linux.it.net.cn
此方案的優(yōu)點(diǎn):
解決了預(yù)編譯宏泛濫的問題,通過二進(jìn)制分層可以將代碼里的所有預(yù)編譯選項(xiàng)去掉。遵循了接口平臺(tái)無關(guān)性的原則。
可見,此方案很好地解決了本文開始提出的兩個(gè)問題。?Linux學(xué)習(xí),http:// linux.it.net.cn
此方案的缺點(diǎn):
每次發(fā)布 Results.so 的時(shí)候,底層庫需要伴隨一起發(fā)布,導(dǎo)致可執(zhí)行包文件數(shù)量成倍增加。而且很多小程序,小工具的發(fā)布往往采取單獨(dú)的二進(jìn)制文件,不允許有底層庫的存在。
解決方案 3: 結(jié)合代理模式,橋接模式和單例模式進(jìn)行優(yōu)化
現(xiàn)在針對(duì)原始問題繼續(xù)進(jìn)行優(yōu)化,擯棄方案 2 采用的分層手法,在單一庫的范圍內(nèi)利用 C++ 多態(tài)特性和設(shè)計(jì)模式進(jìn)行優(yōu)化。
目標(biāo)效果:
源代碼中盡可能減少預(yù)編譯選項(xiàng)出現(xiàn)的頻率,避免因功能擴(kuò)展和平臺(tái)支持的增加導(dǎo)致預(yù)編譯宏數(shù)量爆炸。
完全遵循接口平臺(tái)無關(guān)性的原則。
清單 3. 解決方案 3 調(diào)用端示例代碼如下:
// Procedure.cpp void SomeFunction() { //Common code for all linux ...... ...... XXHost::instance()->SpecialCase1(); //Common code for all linux ...... ...... XXHost::instance()->SpecialCase2(); //Common code for all linux ...... ...... } Linux學(xué)習(xí),http:// linux.it.net.cn
圖 3:方案 3 的具體實(shí)現(xiàn)類圖
此方案結(jié)合改進(jìn)的代理模式(Proxy),橋接模式(Bridge)和單件模式(Singleton),并利用 C++ 封裝、繼承和多態(tài)特性予以實(shí)現(xiàn)。?IT網(wǎng),http://www.it.net.cn
IHost 是頂層抽象接口類,聲明了實(shí)現(xiàn)端需要實(shí)現(xiàn)的功能函數(shù)以及調(diào)用端需要調(diào)用的接口函數(shù)。?Linux學(xué)習(xí),http:// linux.it.net.cn
圖 3 右半部分派生自 IHost 的各個(gè)類為實(shí)現(xiàn)端,在實(shí)現(xiàn)端,?Linux學(xué)習(xí),http:// linux.it.net.cn
為每個(gè) Linux 系統(tǒng)單獨(dú)實(shí)現(xiàn)了一個(gè)類,相互之間無關(guān)聯(lián)性。該類實(shí)現(xiàn)了該操作系統(tǒng)平臺(tái)相關(guān)的功能(SpecialCase1() 和 SpecialCase2()),即實(shí)現(xiàn)了平臺(tái)相關(guān)代碼。每個(gè)實(shí)現(xiàn)類采取單件模式。
Init() 和 terminate() 用來初始化和清理操作。Init() 函數(shù)首先創(chuàng)建自己(單件模式),其次創(chuàng)建左側(cè)代理類(單件模式,見下段描述),最后將自己的內(nèi)存地址通過 SetHost() 函數(shù)交給左側(cè)代理方。
圖 3 左半部分派生自 Host 的各個(gè)類為調(diào)用端,在調(diào)用端,?IT網(wǎng),http://www.it.net.cn
Host 類做了一層封裝,RhelHost 等派生類為實(shí)際的代理者(調(diào)用者),每個(gè) Host 的派生類分別代表一種需求(調(diào)用方),是右側(cè)實(shí)現(xiàn)類的一個(gè)代理,例如 RhelHost 是 RhelOS 的代理,SuseHost 是 SuseOS 的代理,UbuntuHost 是 UbuntuOS 的代理。每個(gè) Host 的派生類采取單件模式。?IT網(wǎng),http://www.it.net.cn
Host 類和 HostImp 類之間采用橋接的設(shè)計(jì)模式,利用 C++ 多態(tài)特性,最后通過 HostImp 類調(diào)用實(shí)現(xiàn)端類的實(shí)現(xiàn)。調(diào)用端的調(diào)用過程如下:
通過 RhelHost 的指針調(diào)用 SpecialCase(),由于 RhelHost::SpecialCase() 沒有覆蓋基類虛函數(shù)的實(shí)現(xiàn),實(shí)際調(diào)用的是 Host::SpecialCase()。
Host 的所有調(diào)用被橋接到 HostImp 對(duì)應(yīng)的函數(shù)。
由 HostImp 類調(diào)用確定的實(shí)現(xiàn)端的某一個(gè)對(duì)象的對(duì)應(yīng)實(shí)現(xiàn)函數(shù)(HostImp 類的 SetHost() 函數(shù)記錄了右側(cè)類的對(duì)象內(nèi)存地址)。
清單 4. 解決方案 3 框架主要源代碼如下:
// Host.h class IHost { public: virtual void SpecialCase1() = 0; virtual void SpecialCase2() = 0; }; class Host : public IHost { public: virtual ~Host() {}; void setHost(IHost* pHost) { m_pImp->setHost(pHost); } virtual void SpecialCase1() { m_pImp->SpecialCase1(); }; virtual void SpecialCase2() { m_pImp->SpecialCase2(); }; protected: Host(HostImp * pImp); private: HostImp* m_pImp; friend class HostImp; }; class RhelHost : public Host { public: static RhelHost* instance(); private: RhelHost(HostImp* pImp); }; RhelHost * RhelHost::instance() { static RhelHost * pThis = new RhelHost (new HostImp()); return pThis; } RhelHost:: RhelHost (HostImp* pImp) : Host(pImp) { } class RhelOS : public IHost { public: static void init() { static RhelOS me; RhelHost::instance()->setHost(&me); } static void term() { RhelHost::instance()->setHost(NULL); } private: virtual void SpecialCase1() { /* Real Operation */ }; virtual void SpecialCase2() { /* Real Operation */ }; }; // HostImp.h class HostImp : public IHost { private: HostImp(const HostImp&); public: HostImp(); virtual ~HostImp() {}; void setHost(IHost* pHost) { m_pHost = pHost; }; virtual void SpecialCase1() { if(m_pHost != NULL) m_pHost->SpecialCase1() } virtual void SpecialCase2() { if(m_pHost != NULL) m_pHost->SpecialCase2() } private: IHost* m_pHost; };
此方案的優(yōu)點(diǎn):
遵循接口平臺(tái)無關(guān)性原則。此方案將各平臺(tái)通用接口提升到最高的抽象層,易于理解和修改。
最大限度地降低預(yù)編譯選項(xiàng)在源代碼中的使用,實(shí)際上,本例中只需要在一處使用預(yù)編譯宏,示例代碼如下:
void Init() { #ifdef RHEL RhelOS::init(); #endif #ifdef SUSE SuseOS::init(); #endif #ifdef UBUNTU UbuntuOS::init(); #endif }
源代碼其他地方不需要添加預(yù)編譯宏。
實(shí)現(xiàn)端和調(diào)用端都通過類的形式進(jìn)行封裝,而且實(shí)現(xiàn)端類和調(diào)用端類都可以自己?jiǎn)为?dú)擴(kuò)展,完成一些各自需要完成的任務(wù),所要保持一致的只是接口層函數(shù)。擴(kuò)展性和封裝性很好。
由此可見,此方案很好地解決了本文開始提出的兩個(gè)問題,而且代碼結(jié)構(gòu)清晰,可維護(hù)型好。
接下來對(duì)上述源代碼繼續(xù)進(jìn)行優(yōu)化。上例 SuseHost/UbuntuHost/SUSEOS/UBUNTUOS 等類的實(shí)現(xiàn)被略去,實(shí)際上這些類的實(shí)現(xiàn)與 RhelHost 和 RHELOS 相似,可以利用宏來進(jìn)一步優(yōu)化框架代碼結(jié)構(gòu)。?Linux學(xué)習(xí),http:// linux.it.net.cn
清單 5. 解決方案 3 框架主要源代碼優(yōu)化:
#define HOST_DECLARE(name) \ class ##nameHost : public Host \ { \ public: \ static ##nameHost* instance(); \ private: \ ##nameHost(HostImp* pImp); \ }; #define HOST_DEFINE(name) \ ##nameHost* ##nameHost::instance() \ { \ static ##nameHost* pThis = new ##nameHost(new HostImp()); \ return pThis; \ } \ ##nameHost::##nameHost(HostImp* pImp) \ : Host(pImp) \ { \ } #define HOST_IMPLEMENTATION(name) \ class ##name##OS : public IHost \ { \ public: \ static void init() \ { \ static ##name##OS me; \ ##nameHost::instance()->setHost(&me); \ } \ static void term() \ { \ ##nameHost::instance()->setHost(NULL); \ } \ private: \ virtual void SpecialCase1(); \ virtual void SpecialCase2(); \ };
使用三個(gè)宏來處理相似代碼。至此,優(yōu)化完成。從源代碼角度來分析,作為實(shí)現(xiàn)端的開發(fā)人員,只需要三步就可以完成操作:
調(diào)用 init() 函數(shù);
使用 #define HOST_IMPLEMENTATION(name);? ? 例如:#define HOST_IMPLEMENTATION(DEBIAN)
實(shí)現(xiàn) DEBIAN::SpecialCase1() 和 DEBIAN::SpecialCase2()。? ? 作為調(diào)用端的開發(fā)人員,也只需要三步就可以完成操作:
使用 #define HOST_DECLARE(name) 進(jìn)行聲明;? ? 例如 : #define HOST_DECLARE(DEBIAN)
使用 #define HOST_DEFINE(name) 進(jìn)行定義;? ? 例如: #define HOST_DEFINE (DEBIAN)
調(diào)用接口。? ? 例如: DEBIANHost::instance()->SpecialCase1();DEBIANHost::instance()->SpecialCase2();
可見,優(yōu)化后方案的代碼清晰,不失為一個(gè)良好的平臺(tái)相關(guān)代碼的解決方案。
由于調(diào)用端和實(shí)現(xiàn)端往往需要傳遞參數(shù),可以通過 SpecialCase1() 函數(shù)的參數(shù)來傳遞參數(shù),同樣的這個(gè)參數(shù)類可以通過橋接的方式予以實(shí)現(xiàn),本文不再詳述,讀者可以自己嘗試。
對(duì)方案 3 的擴(kuò)展
擴(kuò)展 1:對(duì)單一操作系統(tǒng)多對(duì)多的擴(kuò)展
對(duì)于方案 3 的實(shí)現(xiàn),也許有讀者會(huì)問,調(diào)用端只需要 Host 類不需要其派生類即可完成方案 3 中的功能,的確如此,因?yàn)榉桨?3 中的代理類一直是一對(duì)一的關(guān)系,即 RhelHost 代理 RhelOS,Redhat 下只存在這一對(duì)一的關(guān)系。但是實(shí)際情況下,單一系統(tǒng)下很可能存在多對(duì)多的關(guān)系。
例如,在單一操作系統(tǒng)中,可能需要同時(shí)實(shí)現(xiàn)多種風(fēng)格的窗口。實(shí)際上,變成了多對(duì)多的代理關(guān)系。
圖 4:?jiǎn)我徊僮飨到y(tǒng)不同 c 風(fēng)格窗口的實(shí)現(xiàn)類圖
Style1Host 代理 Style1Dialog,Style2Host 代理 Style2Dialog,Style3Host 代理 Style3Dialog,三個(gè)窗口同時(shí)并存,也就是說左側(cè)三個(gè)實(shí)現(xiàn)類的實(shí)例和右側(cè)三個(gè)代理類的實(shí)例同時(shí)存在。可見,方案 3 的設(shè)計(jì)模式擴(kuò)展性良好,實(shí)現(xiàn)端和調(diào)用端都可以在遵循接口不變性的情況下單獨(dú)擴(kuò)展自己的功能。
擴(kuò)展 2:對(duì)多操作系統(tǒng)的擴(kuò)展
方案 3 不僅可以針對(duì) Linux 平臺(tái)相關(guān)代碼進(jìn)行處理,還可以擴(kuò)展到其他諸多場(chǎng)合。例如,現(xiàn)在的程序庫往往需要針對(duì)多個(gè)操作系統(tǒng),包括 Windows, Linux, Mac。每個(gè)操作系統(tǒng)往往使用不同的 GUI 庫,這樣在實(shí)現(xiàn)窗口操作的時(shí)候必然涉及到平臺(tái)相關(guān)代碼。同樣可以用方案 3 予以實(shí)現(xiàn)。
圖 5:多操作系統(tǒng)的實(shí)現(xiàn)類圖
Linux學(xué)習(xí),http:// linux.it.net.cn
總結(jié)
本文開始提出平臺(tái)相關(guān)代碼造成的兩個(gè)問題,接著循序漸進(jìn)提出解決方案。在分析了常用的設(shè)置預(yù)編譯選項(xiàng)方法的利弊的基礎(chǔ)上,提出了一種新的利用 C++ 多態(tài)特性,結(jié)合使用代理模式,橋接模式和單件模式處理平臺(tái)相關(guān)代碼的方案,并對(duì)這一方案予以擴(kuò)展,給讀者提供了一種新的高效的處理平臺(tái)相關(guān)代碼的方法。?Li
?
評(píng)論
查看更多