1. 程式人生 > >表驅動與工廠模式

表驅動與工廠模式

錯誤 south 部門 格式 text emp ctf temp gravity

關於表驅動

? ? ? ? 首次接觸表驅動。還是在畢業不久之後。當時某部門經理給我們解說重構。即《重構:改善既有代碼的設計》一書中簡化條件表達式部分。關於if語句的處理。將其替換為多態形式,比如說工廠模式。

可是即使替換為工廠,switch或者if的推斷依然不能去除,那麽有什麽辦法解決問題呢?

? ? ? ? 當時我還在研究STL源代碼,想到了traits編程技術,能夠在編譯期解決if的推斷問題(盡管有這個想法,可是一直沒有實現成功)。

各路大牛提出了不同的見解,大家基本上都允許一條:使用“表”來解決。

當時見識尚淺,不懂詳細說的是什麽意思,直到我知道了“表驅動”。

? ? ? ? “表驅動”來自於《代碼大全》,此書在我的定義裏,為一本軟件project類的書。表驅動作為單獨一章出現。而且在序言中推薦為0基礎程序猿首讀章節,可見其重要性。

? ? ? ? 首先,為什麽要有表驅動呢?表驅動的目的是避免邏輯語句(if和case),而使用表來查找推斷信息。

那麽為什麽要這麽做呢?《代碼大全》第5.2章節提到,軟件的首要技術使命:管理復雜度。復雜度能夠靠圈復雜度(一個函數可運行路徑的數目)來推斷。詳細要涉及到圖論等等方方面面。不再展開說明。

那麽表驅動的使用就能夠大幅度的減少復雜度。

? ? ? ? 其次,表驅動是什麽?不論什麽能夠用邏輯語句來選擇的事物,都能夠通過查表來選擇。比如說:情況1,選擇事物1;情況2,選擇事物2等等。存儲在表中就是例如以下格式:

情況

事物

1

1

2

2

? ? ? ? 那麽,凡是用if和case來選擇事物的語句。都能夠替換為以下形式:

? ? ? ? Table[選擇的情況i];

? ? ? ? 這樣就能夠直接通過首地址+偏移量直接獲取相應的內容,取消了推斷邏輯。假設要選擇第n個事物,那麽就是首地址+n。直接得到了第n個事物。假設用正常的推斷邏輯。那麽可能須要推斷n次才幹夠得到第n個事物。

? ? ? ? 其它詳細內容,大家能夠自己去百度一下~

?

工廠模式

? ? ? ? 工廠模式來源於《設計模式》,是最最主要的模式之中的一個,也是最最經常使用的模式之中的一個。工廠模式也很easy。先來一個簡單工廠說明一下表驅動的問題,其UML圖例如以下:

技術分享圖片

? ? ? ? 那麽創建Product時。大部分要經過此過程:

Product *product = nullptr;
switch(productType)
{
case TYPE_PRODUCT1:
    product = new(std::nothrow)Product1();
    break;
case TYPE_PRODUCT2:
    product = new(std::nothrow)Product2();
    break;
case TYPE_PRODUCT3:
    product = new(std::nothrow)Product3();
    break;
case TYPE_PRODUCT4:
    product = new(std::nothrow)Product4();
    break;
default:
    break;
}

? ? ? ? 問題就這麽隨著出來了。邏輯語句怎麽用表驅動替換呢?

?

函數指針

? ? ? ? 進入正式主題之前,另一些內容須要解決,由於表裏面存儲信息須要這一部分內容。

? ? ? ? 函數指針想必大家都有所了解,比如以下的代碼:

// 定義一個函數指針
typedef void (*FuncPtr)();
 
// 定義與函數指針相應的函數
void Func()
{
    std::cout <<"Func." << std::endl;
}
 
int main(int argc, char **argv)
{
    // 將函數指針指向相應的函數
    FuncPtr ptr = Func;
    // 調用函數
    ptr();
    return 0;
}

?

工廠表驅動

? ? ? ? 有了產品類型。有了創建產品的方法,那麽怎樣將其寫入表中呢?一般我們會這麽存儲:

TYPE_PRODUCT1

創建TYPE_PRODUCT1類型的函數指針

TYPE_PRODUCT2

創建TYPE_PRODUCT2類型的函數指針

TYPE_PRODUCT3

創建TYPE_PRODUCT3類型的函數指針

TYPE_PRODUCT4

創建TYPE_PRODUCT4類型的函數指針

? ? ? ? 可是,我們在C++語言中應該怎樣實現呢?表能夠用數組,map等方式實現,比如以下的代碼:

typedef Product* (*NewProduct)();
 
struct ProductCreator
{
    int            m_productType;
    NewProduct     m_newProductFuncPtr;
};
 
const ProductCreator PRODUCT_CREATOR[] =
{
    { TYPE_PRODUCT1, newProduct1 },
    { TYPE_PRODUCT2, newProduct2 },
    { TYPE_PRODUCT3, newProduct3 },
    { TYPE_PRODUCT4, newProduct4 },
};

? ? ? ? 可是這樣能夠嗎?由於new會把實際對象創建出來。不能轉化為一個函數指針,所以肯定是不能夠的。怎樣解決呢?

?

使用仿函數

? ? ? ? 百思不得其解,可是是問題總有解決的辦法。要實現不同類型創建不同對象,不就是模板的思想麽?從這個角度出發,問題立即就攻克了~

? ? ? ? 解決方式,使用模板,創建一個仿函數(函數對象),通過函數對象創建實際的對象。

實現代碼例如以下:

typedef Product* (*NewProduct)();
 
template <class T>
struct TypeCreator
{
    static Product *New()
    {
        return(new(std::nothrow) T());
    }
};
 
struct ProductCreator
{
    int            m_productType;
    NewProduct     m_newProductFuncPtr;
};
 
const ProductCreator PRODUCT_CREATOR[] =
{
    { TYPE_PRODUCT1,TypeCreator<Product1>::New },
    { TYPE_PRODUCT2,TypeCreator<Product2>::New },
    { TYPE_PRODUCT3,TypeCreator<Product3>::New },
    { TYPE_PRODUCT4,TypeCreator<Product4>::New },
};

? ? ? ? 這樣。就能夠通過查PRODUCT_CREATOR這個表。取得函數對象。然後調用其方法就能夠取得詳細的對象,比如:

Product *product = PRODUCT_CREATOR[i].m_newProductFuncPtr();

?

使用指向Member function的指針

? ? ? ? 近期看了《深度探索C++對象模型》,收獲頗豐,當看到指向Member function的指針時。突發奇想,果斷來試一把,看看是否能解決此問題。

? ? ? ? 指向Memberfunction的指針,顧名思義,就是指向一個類成員函數的指針,事實上相似於函數指針,其聲明方法例如以下:

class A
{
public:
    void Func() {std::cout << "A Func." << std::endl; }
};
 
int main(int argc, char **argv)
{
    void (A::* funcPtr)();
    funcPtr =&A::Func;
 
    A a;
    (a.*funcPtr)();
        
    A *b = new A;
    (b->*funcPtr)();
 
    return 0;
}

? ? ? ? 看到這種代碼,真是有一種“山窮水盡疑無路,柳暗花明又一村”的感覺吶,大快人心。趕緊來看看是否能解決問題呢?
? ? ? ? 終於結果,失敗了。原因有兩個:

? ? ? ? 1. 實在想不出構造函數的指向Member function的指針怎麽寫。

由於構造函數沒有返回值,可是指向Member function的指針必須要有返回值的定義。

? ? ? ? 2. 還記得C++第一節課老師講過的內容嗎?老實說,一個類會默認自己主動生成構造函數。析構函數,拷貝構造函數。

事實上這個是錯誤的,依據構造函數語義學,一個類僅在下列四種情況下自己主動生成構造函數:

? ? ? ?1> 假設一個類沒有不論什麽構造函數,但它的一個成員內部有默認構造函數。那麽這個類也須要生成默認構造函數,只是這個操作僅在構造函數被調用時才會發生。

? ? ? ? 2> 當基類含有默認構造函數時。子類假設沒有不論什麽構造函數,則需合成默認構造函數。

? ? ? ? 3> 當類含有虛函數時,假設未定義不論什麽構造函數。則需合成默認構造函數。

? ? ? ? 4> 當類有虛繼承時,假設未定義不論什麽構造函數,則需合成默認構造函數。

? ? ? ? 事實上上面前兩點是依賴於後兩點的,為什麽呢?看第一點和第二點,其都要求父類或者成員中包括默認構造。首先。覺得聲明的構造函數不叫默認構造;其次,既然存在默認構造,那麽肯定是第三點或者第四點造成的。所以說第一點和第二點依賴於後兩點。

? ? ? ? 所以說。以下這個類是沒有構造函數的,包括默認構造函數:

class Product
{
public:
         int m_IntVal;
};

? ? ? ? 那麽要去通過一個指向Memberfunction的指針指向構造函數,肯定是失敗的,所以編譯器禁止指向構造函數的指針,並提示消息:

? ? ? ? Error:a constructor or destructor may not have its address taken

?

進一步思考

? ? ? ? 首先,表驅動方法是必須掌握的一個技巧,使用它將帶來程序效率上的提升,代碼的整潔等等各個方面的優點。

? ? ? ? 其次,project的管理必須進行相關方面標準的定義及控制,使用SourceMonitor等工具把握項目質量至關重要。這周員工培訓上,聽老韓這麽多年經驗的總結,讓我深深認識了“沒有銀彈”這個深刻的道理。在“銀彈”沒有造出來的前提下,不論什麽過程都必須嚴格控制。否則將陷入無窮無盡的“焦油坑”。

? ? ? ? 第三。繼續給自己多挖幾個坑吧。假設一直走平地。貌似永遠也登不上高峰。由於同往高峰的路永遠沒有平路。

?

參考書目

? ? ? ? 《代碼大全第二版》 Steve McConnell

? ? ? ? 《STL源代碼剖析》侯捷

? ? ? ? 《重構:改善既有代碼的設計》Martin Fowler

? ? ? ? 《設計模式:可復用面向對象軟件的基礎》GoF四人幫

? ? ? ? 《大話設計模式》程傑

? ? ? ? 《深度探索C++對象模型》Stanley B.Lippman

? ? ? ? 《人月神話》Frederick P.Brooks.Jr.

表驅動與工廠模式