Write test cases for abstract factory using C++

前言

由於 C++ 語言特性,要作出抽象工廠的其一手段就是創建衍生類別的實體後,將其位址轉型爲共同界面的指標再回傳給呼叫者。這在使用上相當直覺,在測試時則需要使用一些小手段來驗證抽象工廠回傳的實體是否是客戶端所要求的。

方法一:dynamic_cast

例如下面的程式碼

#include <iostream>

struct Base
{
    virtual ~Base() {};
  virtual void foo() = 0;
}

struct D1 : Base
{
    virtual void foo() { std::cout << "D1" << std::endl; }
};

struct D2 : Base
{
    virtual void foo() { std::cout << "D2" << std::endl; }
}

int
main(int argc, char* argv[])
{
...
Base* base_ptr = abstractFactory();

if (dynamic_cast<D1*>(base_ptr))
    std::cout << "This is instancce of D1!!" << std::endl;
if (dynamic_cast<D2*>(base_ptr))
    std::cout << "This is instancce of D2!!" << std::endl;
...
}

完美?我想不盡然。這個方法的問題在於繼承結構,如果繼承結構更深,有機會發生可以動態轉型但其實體卻不是該形態的問題。這方法的破綻太大。

方法二:typeid

typeid 是存在 typeinfo 標頭檔內的函式,可以在執行時期獲取輸入物件的屬性,如 name 或是 hash_code (這有點危險,這裏的實作是取決於編譯器,得到的名稱不見得相同也不一定可被人類解讀,甚至有編譯器是不實作 name 呼叫的回傳字串),用起來如下

#include <typeinfo>

...
Base* base_ptr = abstractFactory();

/* Beware here we use *base_ptr to its instance rather than pointer */
if (typeid(*base_ptr) == typeid(D1))
    std::cout << "This is instancce of D1!!" << std::endl;
if (typeid(*base_ptr) == typeid(D2))
    std::cout << "This is instancce of D2!!" << std::endl;
...

typeid 的回傳值是 std::type_info 類別,該類別的設計很詭異,其建構子是不公開的,也就是單就 std::type_info 我們沒辦法將某類別的資訊存下來做後續使用,要做到此事必須再導入一個新類別:type_index,我們可以在 typeindex 標頭檔找到它。

#include <typeinfo>
#include <typeindex>

...
Base* base_ptr = abstractFactory();
std::type_index idxForPtr = typeid(*base_ptr);
std::type_index idxForD1 = typeid(D1);
std::type_index idxForD2 = typeid(D2);

if (idxForPtr == idxForD1)
    std::cout << "This is instancce of D1!!" << std::endl;
if (idxForPtr == idxForD2)
    std::cout << "This is instancce of D2!!" << std::endl;
...

測試

有了上面提到的 typeid 和 std::type_index,要寫測試來驗證抽象工廠回傳的實體是否爲程式設計師所期望的也就不成問題了。

唯一令我感到不太對勁的是 撰寫抽象工廠的測試 這件事,依照物件導向的設計原則來說,就是希望客戶端可以利用統一界面來操作衍生類別,來符合開閉原則且排除不必要的知識,而我們現在卻反過來去瞭解抽象工廠傳回的究竟是哪個衍生類別的實體。或許這是從客戶端程式跟測試端程式觀點和目的上的不同所致。

Comments

comments powered by Disqus