把乘法變成加法(轉自csdn longshanks)

不要誤會,不是用加法重載operator*。(做這種事情的程序員應該立刻開除)。或者任何跟計算有關的事。這裏要講的是另外一個故事。
當你看我這篇帖子的時候,是否想過你的計算機是如何構成的?內存、主板、硬盤、cpu、顯卡、顯示器、光驅、鍵盤、鼠標等等。沒錯,你肯定很熟悉了。那麼,你是否想過電腦廠商爲了生產不同的配置的計算機,準備了多少配件嗎?不好意思,我也不清楚。不過沒關係,我們可以假設。假設內存規格有256、512、1G、2G四種規格(不考慮牌號,後面也一樣);硬盤規格有80G、100G、120G、160G和200G五種規格;顯卡有三種(假設一下,我搞不清現在有多少種顯卡);cpu有五款;顯示器有4種;光驅有5種;鼠標鍵盤就不考慮了。
那麼我們總共可以得到多少種配置呢?很簡單,4*5*3*5*4*5=6000種!當然沒有哪個廠商會推出6000款型號,只是假設一下。那麼總共有多少配件呢?4+5+3+5+4+5=26種。也就是廠商只需管理26種配件,便可以製造出6000個機型。
現在讓我們再假設一下,當初IBM發明PC的時候,一時糊塗,沒有把PC的各個組成部分組件化,所有的組成部分都是焊在一塊電路板上的,包括顯示器。那麼如果一個廠商想獲得這6000種配置的電腦,那麼他們就必須直接生產,並且管理6000種不同的組件(電腦)。
這就是差別,組件化vs非組件化:26對6000。
好,現在回到我們熟悉的軟件(開發)上來。我們在軟件開發是通常也面臨計算機廠商同樣的問題:產品多樣性的問題。即便是同一種軟件,在不同的客戶那裏通常會有不同要求。爲每一個客戶開發不同的軟件,明顯是非常低效的。(不幸的是,這種愚蠢的行爲,在業界幾乎成了慣例)。順便說明一下,這裏的客戶是指用你軟件的人。如果你開發的是庫,那麼客戶就是使用你的庫的人。而本文主要針對的是庫開發這種情況。
假設,我們要開發一個數據庫訪問的包裝庫。爲其它程序員提供方便快捷地訪問數據庫的能力,使他們免於和難纏的ODBC或OleDB打交道。但是,前提是我們的包裝庫不能像ADO.net那樣折損開發人員的訪問能力。
基於這種前提,我們需要考慮數據庫訪問的幾個基本要素。我歸納了一下,大概可以包括這麼幾個:遊標、數據綁定、數據緩衝。爲了簡化,其他細枝末節暫不考慮。同時,只考慮結果集處理部分。下面,我們將考察兩種不同的實現方式:OOP和GP。
先看OOP方式。OOP方式利用多態和後期綁定提供了擴展能力和一致的接口。代碼大概會是這樣:
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;

};
因爲這裏只關心程序的結構,所以只給出聲明,略去定義。MyDBAccessor定義了一個基本的框架,對於不同的特性支持,比如不同的遊標等等,通過在繼承類中重載相應的虛函數實現:
class MyDBFFAccessor//Fast-forward遊標類型
: public MyDBAccessor
{

virtual bool MoveNext(){…}
virtual bool MovePre(){…}
virtual bool MoveFirst(){…}
virtual bool MoveLast(){…}

};
那麼,當我們需要一個支持Fast-forward遊標,自動綁定,並且按行緩衝的數據庫訪問類時,我們定義瞭如下的類:
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遊標,字符串綁定(所有類型轉換成字符串),塊緩衝的數據庫訪問類,那麼就再定義一個繼承類。
問題來了,遊標類型至少有8種,假設默認綁定方式有5種(自動、寬/窄字符串綁定、xml綁定、手工綁定),數據緩衝方式有3種(行緩衝、塊緩衝、數組緩衝)。
那麼我們得定義多少個繼承類呢?8*5*3+1=121。Mission Impossible,除非你有ms那樣的資源。
現在,我們來看看GP(範型編程)方式會不會好一些。我們先定義一個類模板:
template<class Cursor, class Binder, class RowBuffer>
class MyDBAccessor
: public Cursor, public Binder, public RowBuffer
{

};
應該看出來了吧,模板MyDBAccessor繼承自模板類型參數Cursor、Binder、RowBuffer。而這三個模板參數分別對應了遊標管理類、綁定類和行緩衝類。根據前面的假設,我們定義了8種遊標管理類:
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) {…}
};
細心的人會發現這些遊標管理類的接口(成員聲明)都不一樣,一會兒會告訴你爲什麼。
其他的綁定類和數據緩衝類都依次定義。當我們需要一個支持Fast-forward遊標,自動綁定,並且按行緩衝的數據庫訪問類時,只需用相應的類實例化模板即可:
MyDBAccessor<FastForwardCursor, DynamicBinder, SingleRowBuffer>da;

如果我們需要一個支持Dynamic遊標,字符串綁定(所有類型轉換成字符串),快緩衝的數據庫訪問類,也很方便:
MyDBAccessor<DynamicCursor, StringBinder, BulkBuffer>da;

在GP方式中,我們只需定義8+5+3+1=17個類和模板,即可實現OOP方式中121類定義才能達到的效果。
非常好吧。還不止於此。假設你希望用DynamicCursor遊標訪問數據庫,但是寫錯了變成了這樣:
MyDBAccessor<FastForwardCursor, DynamicBinder, SingleRowBuffer>da;

da.MoveFirst();//啊呀!
不要告訴我你不會犯這種低級錯誤。這種錯誤每時每刻都在發生,最可能的一種情況就是軟件的設計改了,由fast-forward遊標改成dynamic遊標,而你卻忘了修改da的聲明。
此時,代碼不會編譯通過。因爲MyDBAccessor繼承自遊標管理類,並從遊標管理類繼承了操縱遊標的成員函數。於是,根據fast-forward的定義:只進不退,所以沒有MoveFirst()函數(也不需要,對吧)。因此,MyDBAccessor<FastForwardCursor, DynamicBinder, SingleRowBuffer>也沒有這個函數。那麼da.MoveFirst()便會引發編譯錯誤。
很多初學者可能不喜歡這種設計,因爲他們非常害怕編譯器錯誤。就好像編譯器是他們中學語文老師一樣。其實,你應該感謝這個編譯錯誤,它在第一時間幫你消除了一個潛在的bug。如果我們在FastForwardCursor中加上MoveFirst()這個函數,編譯自然沒有問題。但在運行時,這句代碼肯定會引發一個運行時錯誤。運氣好的話在你測試的時候,運氣不好的話可能會在你客戶心情最差的時候發生。這個後果,…,哎呀呀。
另外,即使在你測試的時候發生,你也會被迫用幾十上百個單步追查問題的原因,以至於把你週末約會女朋友的心情都搞壞了。&#61514;
好了,乘法變加法的把戲變完了。簡單地講,就是你可以利用模板、繼承模板參數,以及多繼承等技術,將一些基本的要素組合起來,構成一個複雜的,功能完整的類。用最少的代碼作最多的事。這種技術是由C++領域的先鋒官Andrei Alexandresu提出來的,稱爲policy。更詳細的內容,可以參考他的《Modren C++ Design》,裏面有很詳細的講解和案例。不過得做好心理準備,接受大劑量模板。

發佈了16 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章