張欲蓉,嚴(yán) 華
(中國電子科技集團(tuán)公司51所,上海 201802)
數(shù)據(jù)存儲(chǔ)是各種計(jì)算機(jī)應(yīng)用系統(tǒng)的一個(gè)重要功能。幾乎所有的計(jì)算機(jī)應(yīng)用系統(tǒng)都需要將各種數(shù)據(jù)信息存儲(chǔ)為不同格式的文件。在存儲(chǔ)過程中,數(shù)據(jù)導(dǎo)出與數(shù)據(jù)保存的代碼很容易高度耦合,很難分別對(duì)數(shù)據(jù)導(dǎo)出與數(shù)據(jù)保存部分單獨(dú)進(jìn)行修改、擴(kuò)充與重用。本文所述的數(shù)據(jù)存儲(chǔ)設(shè)計(jì),運(yùn)用bridge和decorator模式,將數(shù)據(jù)導(dǎo)出與數(shù)據(jù)保存解耦,使設(shè)計(jì)易維護(hù)、易擴(kuò)展、易復(fù)用。
在某個(gè)典型的電子對(duì)抗領(lǐng)域的應(yīng)用中,有上級(jí)指控命令、信號(hào)分選結(jié)果、系統(tǒng)工作日志等數(shù)據(jù)需要存儲(chǔ),這些數(shù)據(jù)可能需要被存儲(chǔ)為多種格式的文件,如*.xls、*.txt、*.html或*.mdb等。實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)功能的最常見的2種設(shè)計(jì)如下:
設(shè)計(jì)1:設(shè)計(jì)一個(gè)模塊,讓其提供多個(gè)接口來滿足需要,這種設(shè)計(jì)缺點(diǎn)很明顯,降低了代碼的內(nèi)聚性,使之趨于不良耦合,可重用性和可靠性大打折扣。
圖1 設(shè)計(jì)2的設(shè)計(jì)類圖
根據(jù)這種設(shè)計(jì),假設(shè)系統(tǒng)數(shù)據(jù)的種類為M,文件格式的種類為N,則一共要設(shè)計(jì)M×N個(gè)類才能滿足要求。這種設(shè)計(jì)存在如下問題:(1)每增加一種待導(dǎo)出的數(shù)據(jù)或增加一種文件導(dǎo)出格式,需要增加多個(gè)類;(2)如果對(duì)待導(dǎo)出數(shù)據(jù)或文件的保存格式進(jìn)行修改將影響與之相關(guān)的多個(gè)類;(3)隨著M或N值的增加,最終設(shè)計(jì)類的個(gè)數(shù)會(huì)增長為不可控制的龐然大物。
本文所要解決的問題就是提出一種設(shè)計(jì),這種設(shè)計(jì)能減少類的數(shù)量,降低類間的耦合度,保持對(duì)象的內(nèi)聚性,易理解,易管理,減少變化帶來的影響,提高重用性[1]。
軟件設(shè)計(jì)真正要做的許多內(nèi)容,就是發(fā)現(xiàn)職責(zé)并把職責(zé)相互分離[2]。將待導(dǎo)出的數(shù)據(jù)視為數(shù)據(jù)源,將數(shù)據(jù)存儲(chǔ)的過程分解為導(dǎo)出和保存2步:導(dǎo)出的職責(zé)是按需求過濾、提取、組織數(shù)據(jù),保存的職責(zé)是將組織好的數(shù)據(jù)保存為不同格式文件。經(jīng)分解后2個(gè)職責(zé)都比較單一,更能適應(yīng)變化的發(fā)生。數(shù)據(jù)存儲(chǔ)的過程如圖2所示。
圖2 數(shù)據(jù)存儲(chǔ)示意圖
設(shè)計(jì)類圖見圖3。
圖3 設(shè)計(jì)類圖
這里設(shè)計(jì)了2個(gè)抽象基類:CExport和CSave。CExport提供數(shù)據(jù)導(dǎo)出的接口,CSave提供數(shù)據(jù)保存的接口。CDB代表數(shù)據(jù)源的抽象,設(shè)計(jì)時(shí)應(yīng)讓其提供接口,使其他對(duì)象能夠獲取數(shù)據(jù)源的數(shù)據(jù)。
CExport派生出CExport Cmd、CExportSignal和CExport Log等子類,CExportCmd子類負(fù)責(zé)導(dǎo)出上級(jí)指控命令,CExportSignal負(fù)責(zé)導(dǎo)出信號(hào)分選結(jié)果,CExport Log負(fù)責(zé)導(dǎo)出系統(tǒng)工作日志。
CSave派生出 CSaveXls、CSave Txt、CSave Html和CSave Mdb等子類,這些子類分別負(fù)責(zé)將導(dǎo)出的數(shù)據(jù)保存為xls、txt、html和mdb等格式。
有些數(shù)據(jù)文件可能需要裝飾,例如:數(shù)據(jù)文件需要增加標(biāo)題或結(jié)尾,于是設(shè)計(jì)1個(gè)抽象類CDecorat-or Export,它是CExport的子類,用于提供統(tǒng)一的裝飾接口。從CDecorator Export派生出2個(gè)子類:CDecorator Export Topic和CDecorator Export Tail。CDecorator Export Topic用于增加標(biāo)題,CDecorator Export Tail用于增加結(jié)尾。如果需要實(shí)現(xiàn)其他裝飾功能,從CDecorator Export類派生出相應(yīng)裝飾作用的類即可。
按照?qǐng)D1的設(shè)計(jì),每增加一種待導(dǎo)出數(shù)據(jù)或文件導(dǎo)出格式,就需要增加多個(gè)類,最終將導(dǎo)致類的數(shù)量龐大。且一旦有變化發(fā)生,將引起多個(gè)類一起變化。究其原因,就是設(shè)計(jì)類職責(zé)過多,耦合過于緊密。緊密耦合導(dǎo)致脆弱的設(shè)計(jì),當(dāng)變化發(fā)生時(shí),設(shè)計(jì)不能適應(yīng)這種變化。事實(shí)上,完全可以找出哪些是數(shù)據(jù)導(dǎo)出,哪些是數(shù)據(jù)保存,然后進(jìn)行職責(zé)分離,使設(shè)計(jì)符合單一職責(zé)原則[2]。
e.g. A: The dinner is ready. Tim has not made it.Can anybody give him a call?
軟件設(shè)計(jì)的一個(gè)重要原則是依賴倒置原則:高層模塊不應(yīng)該依賴低層模塊,2個(gè)都應(yīng)該依賴抽象;抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。針對(duì)接口編程,不要對(duì)實(shí)現(xiàn)編程。相對(duì)于實(shí)現(xiàn)細(xì)節(jié)的多變性,抽象的接口要穩(wěn)定得多。依據(jù)這個(gè)原則,設(shè)計(jì)抽象類CSave和CExport,CExport提供數(shù)據(jù)導(dǎo)出的接口;CSave提供數(shù)據(jù)保存的接口。讓CExport基類維持一個(gè)指向CSave類的指針。這樣數(shù)據(jù)導(dǎo)出CExport的各個(gè)子類對(duì)數(shù)據(jù)保存的依賴關(guān)系都終止于抽象類或接口。正是由于這個(gè)抽象的依賴關(guān)系,以及子類型對(duì)父類型的可替換,實(shí)現(xiàn)了數(shù)據(jù)導(dǎo)出和數(shù)據(jù)保存的分離。設(shè)計(jì)類圖見圖4,這種設(shè)計(jì)是bridge模式的一個(gè)應(yīng)用。
圖4 應(yīng)用bridge模式實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出與數(shù)據(jù)保存的分離
CExport的子類CExportSignal、CExportCmd等和CSave的子類CSaveXls、CSave Html等之間沒有固定的綁定關(guān)系,可以在程序運(yùn)行過程中根據(jù)需要選擇或切換。
下面以將信號(hào)分選結(jié)果導(dǎo)出保存為Txt和Html為例進(jìn)行說明:
//對(duì)數(shù)據(jù)保存的依賴終止于抽象類CSave,可傳入指向CSave的子類的指針,將數(shù)據(jù)保存為不同格式文件。
實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出與數(shù)據(jù)保存功能的客戶代碼如下:
//傳入指向CSave Txt類的指針,實(shí)現(xiàn)子類對(duì)父類的替換,將信號(hào)分選結(jié)果導(dǎo)出保存為txt文件。
m_p Export->Export(m_pSave Txt);
//傳入指向CSave Html類的指針,實(shí)現(xiàn)子類對(duì)父類的替換,將信號(hào)分選結(jié)果導(dǎo)出保存為html文件,不需要對(duì)CExportSignal作修改。
m_pExport->Export(m_pSave Html);
采用bridge模式的核心意圖,就是將數(shù)據(jù)保存獨(dú)立出來,減少與數(shù)據(jù)導(dǎo)出的耦合,讓它與數(shù)據(jù)導(dǎo)出各自獨(dú)立地變化,并且該變化對(duì)其他部分不會(huì)產(chǎn)生影響。
CExport的各個(gè)子類只負(fù)責(zé)按需求過濾、提取、組織數(shù)據(jù),將保存功能分配給了CSave的各個(gè)子類,但是它們能夠共同協(xié)作并提供某種良好界定的行為[3],因此它們具有高內(nèi)聚的性質(zhì)。每增加一種待導(dǎo)出數(shù)據(jù),只需要設(shè)計(jì)一個(gè)從CExport派生的子類即可,其他所有的類均不需要任何改變。同樣,每增加一種文件導(dǎo)出格式,只需要增加一個(gè)從CSave派生出的子類即可,其他所有類也不需要改變。職責(zé)的合理分配與依賴倒置原則的應(yīng)用,降低了CExport的子類和CSave的子類之間的耦合性,保持了對(duì)象的內(nèi)聚性。
CSave Txt、CSave Html、CSaveXls及 CSaveDoc等承擔(dān)保存文件職責(zé)的類經(jīng)過測(cè)試成熟后,便可被其他項(xiàng)目所復(fù)用。
導(dǎo)出日志時(shí),有時(shí)需要加上導(dǎo)出日期、日志名稱等信息,或是添加在標(biāo)題處,或是添加在結(jié)尾處,有時(shí)也可能并不需要增加任何裝飾。即裝飾功能是動(dòng)態(tài)增加的。如何實(shí)現(xiàn)這個(gè)動(dòng)態(tài)裝飾功能呢?常見的方法是在數(shù)據(jù)導(dǎo)出類中增加接口。但是這樣就必須修改各個(gè)數(shù)據(jù)導(dǎo)出類的接口,增加新的邏輯,導(dǎo)致原有類的復(fù)雜度增加,客戶代碼也需要適應(yīng)這個(gè)新的邏輯和接口,一起作修改。當(dāng)每次增加新的裝飾功能時(shí),修改又會(huì)再次發(fā)生在同樣的地方。在設(shè)計(jì)中應(yīng)用了decorator模式來隔離這些變化,使變化產(chǎn)生的影響降到最低。
軟件設(shè)計(jì)應(yīng)遵循的另一個(gè)原則是:開放-封閉原則,就是說軟件實(shí)體(類、模塊、函數(shù)等等)應(yīng)該可以擴(kuò)展,但是不可修改[2]。即對(duì)于擴(kuò)展是開放的,對(duì)于更改是封閉的。但是無論模塊設(shè)計(jì)得多么“封閉”,都會(huì)存在一些無法對(duì)之封閉的變化。既然不可能完全封閉,就創(chuàng)建抽象來隔離變化,即找出變化并封裝變化[4]。這里的變化就是動(dòng)態(tài)增加的裝飾功能。設(shè)計(jì)的出發(fā)點(diǎn)就是讓CExport類及其子類無需知道裝飾功能的存在,即不對(duì)CExport類及其子類作任何修改,保證它們封閉性的同時(shí),提供擴(kuò)展的開放性。設(shè)計(jì)類圖見圖5,這種設(shè)計(jì)是decorator模式的一個(gè)應(yīng)用。
圖5 應(yīng)用decorator模式實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出的動(dòng)態(tài)增加
設(shè)計(jì)CDecorator Export類,它是用于裝飾的抽象基類,它還是CExport的子類,與被裝飾的CExport及其子類具有相同的接口。它同時(shí)維持一個(gè)指向CExport對(duì)象的指針,并改寫了從CExport繼承來的Export接口,改寫的示例代碼如下。這為在不改變CExport對(duì)象的情況下給CExport對(duì)象增加職責(zé)提供了基礎(chǔ)。
將CExport對(duì)象看作一個(gè)核,CDecorator Export對(duì)象看作它的外殼,通過套上外殼可以改變核的行為。子類CDecorator Export Topic和CDecorator Export Tail從CDecorator Export派生,將它們實(shí)例化后的對(duì)象就是核的外殼。它們提供用于裝飾的接口,給CExport對(duì)象添加新的職責(zé),一個(gè)負(fù)責(zé)增加標(biāo)題,一個(gè)負(fù)責(zé)增加結(jié)尾。它們均改寫了從CDecorator Export繼承過來的Export接口,實(shí)現(xiàn)裝飾功能。示例代碼如下:
如果需要實(shí)現(xiàn)其他裝飾功能,只需增加一個(gè)從CDecorator Export派生出來的裝飾類即可,原有的實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出與數(shù)據(jù)保存功能的類不需要作任何改動(dòng)。對(duì)于客戶代碼來說,通過與2.1中示例代碼的比較可知,只有實(shí)例化CExport類為對(duì)象這一個(gè)地方有改動(dòng),由于裝飾類是CExport的子類,它們具有相同接口,客戶代碼的其余地方均不需要改動(dòng)。
為了增加靈活性和封裝變化,本文在設(shè)計(jì)中選擇了細(xì)粒度的類,每個(gè)類都有比較明晰、單一的職責(zé)。使用這些細(xì)粒度類提供了更為靈活的策略,也提供了更多的可復(fù)用性。
在應(yīng)用中有一點(diǎn)要特別注意,由于裝飾對(duì)象的接口與它所裝飾的CExport對(duì)象的接口是一致的,保持CExport類的簡單性很重要,否則會(huì)使裝飾類擁有一些它們并不需要的功能而難以使用,也使系統(tǒng)付出不必要的代價(jià)。設(shè)計(jì)時(shí),CExport類應(yīng)集中于定義接口,盡量少地維護(hù)數(shù)據(jù),對(duì)數(shù)據(jù)的表示和維護(hù)應(yīng)延遲到其子類中。當(dāng)CExport類很龐大時(shí),需要考慮使用別的方法來動(dòng)態(tài)增加裝飾功能。
設(shè)計(jì)模式的使用有效地提高了設(shè)計(jì)的質(zhì)量,使整個(gè)設(shè)計(jì)具有清晰的結(jié)構(gòu)、良好的擴(kuò)展性和易復(fù)用性。本文提出的數(shù)據(jù)存儲(chǔ)設(shè)計(jì)已成功運(yùn)用于多個(gè)項(xiàng)目,對(duì)于縮短項(xiàng)目開發(fā)周期具有積極的意義。
[1]Craig Larman.Applying UML and Parrerns[M].北京:機(jī)械工業(yè)出版社,2006.
[2]Martin Robert C.敏捷軟件開發(fā):原則、模式與實(shí)踐[M].北京:清華大學(xué)出版社,2003.
[3]Booch.Object Solutions:Managing The Object-Oriented Project[M].New York:Addision-Wesley,1996.
[4]Alan Shalloway,Trott James R.Design Patterns Explained:A New Perspective on Object-oriented Design[M].徐言聲譯.北京:人民郵電出版社,2006.
[5]Erich G,Richard H,Ralph J,et al.Design Patterns:Elements of Reusable Object-oriented Software[M].北京:機(jī)械工業(yè)出版社,2005.