把乘法變成加法 不要誤會,不是用加法重載operator*。(做這種事情的程序員應(yīng)該立刻開除)?;蛘呷魏胃嬎阌嘘P(guān)的事。這里要講的是另外一個故事。 當(dāng) 你看我這篇帖子的時候,是否想過你的計算機(jī)是如何構(gòu)成的?內(nèi)存、主板、硬盤、cpu、顯卡、顯示器、光驅(qū)、鍵盤、鼠標(biāo)等等。沒錯,你肯定很熟悉了。那么, 你是否想過電腦廠商為了生產(chǎn)不同的配置的計算機(jī),準(zhǔn)備了多少配件嗎?不好意思,我也不清楚。不過沒關(guān)系,我們可以假設(shè)。假設(shè)內(nèi)存規(guī)格有256、512、 1G、2G四種規(guī)格(不考慮牌號,后面也一樣);硬盤規(guī)格有80G、100G、120G、160G和200G五種規(guī)格;顯卡有三種(假設(shè)一下,我搞不清現(xiàn) 在有多少種顯卡);cpu有五款;顯示器有4種;光驅(qū)有5種;鼠標(biāo)鍵盤就不考慮了。 那么我們總共可以得到多少種配置呢?很簡單,4*5*3*5*4*5=6000種!當(dāng)然沒有哪個廠商會推出6000款型號,只是假設(shè)一下。那么總共有多少配件呢?4+5+3+5+4+5=26種。也就是廠商只需管理26種配件,便可以制造出6000個機(jī)型。 現(xiàn)在讓我們再假設(shè)一下,當(dāng)初IBM發(fā)明PC的時候,一時糊涂,沒有把PC的各個組成部分組件化,所有的組成部分都是焊在一塊電路板上的,包括顯示器。那么如果一個廠商想獲得這6000種配置的電腦,那么他們就必須直接生產(chǎn),并且管理6000種不同的組件(電腦)。 這就是差別,組件化vs非組件化:26對6000。 好, 現(xiàn)在回到我們熟悉的軟件(開發(fā))上來。我們在軟件開發(fā)是通常也面臨計算機(jī)廠商同樣的問題:產(chǎn)品多樣性的問題。即便是同一種軟件,在不同的客戶那里通常會有 不同要求。為每一個客戶開發(fā)不同的軟件,明顯是非常低效的。(不幸的是,這種愚蠢的行為,在業(yè)界幾乎成了慣例)。順便說明一下,這里的客戶是指用你軟件的 人。如果你開發(fā)的是庫,那么客戶就是使用你的庫的人。而本文主要針對的是庫開發(fā)這種情況。 假設(shè),我們要開發(fā)一個數(shù)據(jù)庫訪問的包裝庫。為其它程序員提供方便快捷地訪問數(shù)據(jù)庫的能力,使他們免于和難纏的ODBC或OleDB打交道。但是,前提是我們的包裝庫不能像ADO.net那樣折損開發(fā)人員的訪問能力。 基于這種前提,我們需要考慮數(shù)據(jù)庫訪問的幾個基本要素。我歸納了一下,大概可以包括這么幾個:游標(biāo)、數(shù)據(jù)綁定、數(shù)據(jù)緩沖。為了簡化,其他細(xì)枝末節(jié)暫不考慮。同時,只考慮結(jié)果集處理部分。下面,我們將考察兩種不同的實(shí)現(xiàn)方式:OOP和GP。 先看OOP方式。OOP方式利用多態(tài)和后期綁定提供了擴(kuò)展能力和一致的接口。代碼大概會是這樣: class MyDBAccessor { … virtual bool MoveNext(); virtual bool MovePre(); virtual bool MoveFirst(); virtual bool MoveLast(); virtual bool GetData(const string& field, void** data); virtual bool GetData(int field, void** data); virtual bool SetData(const string& field, void* data) {…} virtual bool SetData(int field, void* data) {…} virtual void BindColumn(const string& field, DBType type); virtual void BindColumn(int field, DBType type); private: virtual void DefaultBind()=0; … }; 因?yàn)檫@里只關(guān)心程序的結(jié)構(gòu),所以只給出聲明,略去定義。MyDBAccessor定義了一個基本的框架,對于不同的特性支持,比如不同的游標(biāo)等等,通過在繼承類中重載相應(yīng)的虛函數(shù)實(shí)現(xiàn): class MyDBFFAccessor//Fast-forward游標(biāo)類型 : public MyDBAccessor { … virtual bool MoveNext(){…} virtual bool MovePre(){…} virtual bool MoveFirst(){…} virtual bool MoveLast(){…} … }; 那么,當(dāng)我們需要一個支持Fast-forward游標(biāo),自動綁定,并且按行緩沖的數(shù)據(jù)庫訪問類時,我們定義了如下的類: class MyDB_FF_Dyn_Row : public MyDBAccessor { … virtual bool MoveNext(){…} virtual bool MovePre(){…} virtual bool MoveFirst(){…} virtual bool MoveLast(){…} virtual bool GetData(const string& field, void** data) {…} virtual bool GetData(int field, void** data) {…} virtual bool SetData(const string& field, void* data) {…} virtual bool SetData(int field, void* data) {…} private: virtual void DefaultBind(){…} … }; 如果我們需要一個支持Dynamic游標(biāo),字符串綁定(所有類型轉(zhuǎn)換成字符串),塊緩沖的數(shù)據(jù)庫訪問類,那么就再定義一個繼承類。 問題來了,游標(biāo)類型至少有8種,假設(shè)默認(rèn)綁定方式有5種(自動、寬/窄字符串綁定、xml綁定、手工綁定),數(shù)據(jù)緩沖方式有3種(行緩沖、塊緩沖、數(shù)組緩沖)。 那么我們得定義多少個繼承類呢?8*5*3+1=121。Mission Impossible,除非你有ms那樣的資源。 現(xiàn)在,我們來看看GP(范型編程)方式會不會好一些。我們先定義一個類模板: template<class Cursor, class Binder, class RowBuffer> class MyDBAccessor : public Cursor, public Binder, public RowBuffer { … }; 應(yīng)該看出來了吧,模板MyDBAccessor繼承自模板類型參數(shù)Cursor、Binder、RowBuffer。而這三個模板參數(shù)分別對應(yīng)了游標(biāo)管理類、綁定類和行緩沖類。根據(jù)前面的假設(shè),我們定義了8種游標(biāo)管理類: class FastForwardCursor { public: bool MoveNext(); bool MoveLast(); bool GetData(const string& field, void** data); bool GetData(int field, void** data); bool SetData(const string& field, void* data) {…} bool SetData(int field, void* data) {…} }; class FastForwardReadOnlyCursor { public: bool MoveNext(); bool MoveLast(); bool GetData(const string& field, void** data); bool GetData(int field, void** data); }; … class DynamicCursor { public; bool MoveNext(); bool MovePre(); bool MoveLast(); bool MoveFirst(); bool GetData(const string& field, void** data); bool GetData(int field, void** data); bool SetData(const string& field, void* data) {…} bool SetData(int field, void* data) {…} }; 細(xì)心的人會發(fā)現(xiàn)這些游標(biāo)管理類的接口(成員聲明)都不一樣,一會兒會告訴你為什么。 其他的綁定類和數(shù)據(jù)緩沖類都依次定義。當(dāng)我們需要一個支持Fast-forward游標(biāo),自動綁定,并且按行緩沖的數(shù)據(jù)庫訪問類時,只需用相應(yīng)的類實(shí)例化模板即可: MyDBAccessor<FastForwardCursor, DynamicBinder, SingleRowBuffer>da; … 如果我們需要一個支持Dynamic游標(biāo),字符串綁定(所有類型轉(zhuǎn)換成字符串),快緩沖的數(shù)據(jù)庫訪問類,也很方便: MyDBAccessor<DynamicCursor, StringBinder, BulkBuffer>da; … 在GP方式中,我們只需定義8+5+3+1=17個類和模板,即可實(shí)現(xiàn)OOP方式中121類定義才能達(dá)到的效果。 非常好吧。還不止于此。假設(shè)你希望用DynamicCursor游標(biāo)訪問數(shù)據(jù)庫,但是寫錯了變成了這樣: MyDBAccessor<FastForwardCursor, DynamicBinder, SingleRowBuffer>da; … da.MoveFirst();//啊呀! 不要告訴我你不會犯這種低級錯誤。這種錯誤每時每刻都在發(fā)生,最可能的一種情況就是軟件的設(shè)計改了,由fast-forward游標(biāo)改成dynamic游標(biāo),而你卻忘了修改da的聲明。 此 時,代碼不會編譯通過。因?yàn)镸yDBAccessor繼承自游標(biāo)管理類,并從游標(biāo)管理類繼承了操縱游標(biāo)的成員函數(shù)。于是,根據(jù)fast-forward的 定義:只進(jìn)不退,所以沒有MoveFirst()函數(shù)(也不需要,對吧)。因此,MyDBAccessor<FastForwardCursor, DynamicBinder, SingleRowBuffer>也沒有這個函數(shù)。那么da.MoveFirst()便會引發(fā)編譯錯誤。 很 多初學(xué)者可能不喜歡這種設(shè)計,因?yàn)樗麄兎浅:ε戮幾g器錯誤。就好像編譯器是他們中學(xué)語文老師一樣。其實(shí),你應(yīng)該感謝這個編譯錯誤,它在第一時間幫你消除了 一個潛在的bug。如果我們在FastForwardCursor中加上MoveFirst()這個函數(shù),編譯自然沒有問題。但在運(yùn)行時,這句代碼肯定會 引發(fā)一個運(yùn)行時錯誤。運(yùn)氣好的話在你測試的時候,運(yùn)氣不好的話可能會在你客戶心情最差的時候發(fā)生。這個后果,…,哎呀呀。 另外,即使在你測試的時候發(fā)生,你也會被迫用幾十上百個單步追查問題的原因,以至于把你周末約會女朋友的心情都搞壞了。 好 了,乘法變加法的把戲變完了。簡單地講,就是你可以利用模板、繼承模板參數(shù),以及多繼承等技術(shù),將一些基本的要素組合起來,構(gòu)成一個復(fù)雜的,功能完整的 類。用最少的代碼作最多的事。這種技術(shù)是由C++領(lǐng)域的先鋒官Andrei Alexandresu提出來的,稱為policy。更詳細(xì)的內(nèi)容,可以參考他的《Modren C++ Design》,里面有很詳細(xì)的講解和案例。不過得做好心理準(zhǔn)備,接受大劑量模板。 |
|
來自: ShangShujie > 《c》