2010-07-26 66 views
0

我有一組類描述了一組邏輯框,它們可以容納東西並對它們執行操作。我有測試C++類的功能

struct IBox // all boxes do these 
{ 
    .... 
} 

struct IBoxCanDoX // the power to do X 
{ 
    void x(); 
} 

struct IBoxCanDoY // the power to do Y 
{ 
    void y(); 
} 

我不知道什麼是「最好的」或可能對這些類的客戶端它只是「喜歡」的成語來處理這些可選功能

一)

if(typeid(box) == typeid(IBoxCanDoX)) 
    { 
     IBoxCanDoX *ix = static_cast<IBoxCanDoX*>(box); 
     ix->x(); 
    } 

b)

IBoxCanDoX *ix = dynamic_cast<IBoxCanDoX*>(box); 
    if(ix) 
    { 
     ix->x(); 
    } 

C)

if(box->canDoX()) 
{ 
    IBoxCanDoX *ix = static_cast<IBoxCanDoX*>(box); 
    ix->x(); 
} 

d)不同類結構現在

struct IBox 
{ 
    void x(); 
    void y(); 
} 
... 
box->x(); /// ignored by implementations that dont do x 

E)相同,除了

box->x() // 'not implemented' exception thrown 

六)明確的測試功能

if(box->canDoX()) 
{ 
    box->x(); 
} 

我肯定還有其他的了。

編輯:

只是爲了讓使用更清晰的情況下

我揭露這些東西通過交互式用戶界面給最終用戶。他們可以輸入'make box do X'。我需要知道盒子是否可以做x。或者,我需要禁用「使當前框做X」命令

EDIT2:THX所有回答者

諾亞·羅伯茨指出,(一)不工作(解釋了我的一些問題!)。 我落得這樣做(b)和輕微變異

template<class T> 
    T* GetCurrentBox() 
    { 
     if (!current_box) 
      throw "current box not set"; 
     T* ret = dynamic_cast<T*>(current_box); 
     if(!ret) 
      throw "current box doesnt support requested operation"; 
     return ret; 
    } 
    ... 
    IBoxCanDoX *ix = GetCurrentBox<IBoxCanDoX>(); 
    ix->x(); 

,讓UI管道處理很好地與例外(我不是真的拋出赤裸裸的字符串)。

我還打算探索遊客

+1

你正在處理指針......好像你忘了把'*'ehm ...無處不在?! – smerlin 2010-07-26 16:36:08

+0

您是否需要在運行時選擇不同的*框?也就是說,你需要運行時多態還是一切都可以在編譯時解決?這是你必須回答的第一個問題,因爲這會引導你進入動態或靜態多態類型的解決方案(即繼承和虛擬方法與模板) – 2010-07-26 17:13:47

回答

1

如果您使用的是「我」前綴表示「接口」,因爲它會在Java中,這將在C++抽象的基地來完成的意思,那麼你的第一個選項將無法正常工作....所以,一個人的出。儘管我已經使用過它。

不要做'd',它會污染你的層次結構。保持界面清潔,你會很高興你做到了。因此,車輛類沒有踏板()功能,因爲只有一些車輛可以踏板。如果客戶端需要使用pedal()函數,那麼它確實需要知道可以使用哪些類。

保持掃清道路「E」出於同樣的原因爲「d」 PLUS,它違反了里氏替換原則的。如果客戶端在調用之前需要檢查一個類是否對pedal()進行響應,以便它不會爆炸,那麼最好的方法是嘗試將其轉換爲具有該函數的對象。 'f'與支票一樣。

'c'是多餘的。如果您的層次結構設置爲應該如此,那麼投射到ICanDoX就足以檢查x是否可以執行X()。

因此,'b'成爲您給出選項的答案。然而,正如Gladfelter所表明的那樣,在你的文章中還有一些你沒有考慮過的選項。

編輯說明:我沒有注意到'c'使用了static_cast而不是動態的。正如我在回答中提到的那樣,dynamic_cast版本更清晰,應該是首選的,除非特殊情況另有規定。它與以下選項相似,因爲它會污染基礎接口。

編輯2:我要指出,對於「A」,我一直用它,但我不使用靜態類型像你這樣在您的文章。任何時候我使用typeid來分割基於類型的流,它總是基於在運行時註冊的東西。例如,打開正確的對話框以編輯某個未知類型的對象:對話管理器根據其編輯的類型向工廠註冊。這使我無需在添加/刪除/更改對象時更改任何流控制代碼。我通常不會在不同的情況下使用這個選項。

+0

爲什麼不(a)工作? – pm100 2010-07-26 18:14:23

+1

@pm - 因爲如果變量是接口的子類,那麼你不會得到那個typeid。 – 2010-07-26 19:09:04

+0

啊 - 我沒有意識到這一點。我認爲如果對象可以被轉換成這種類型,那將是真的。我再一次期待C++能像c#或jave一樣 – pm100 2010-07-26 21:52:32

4

我建議在C雙調度問題,像這樣++ Visitor模式:

class IVisitor 
{ 
public: 
    virtual void Visit(IBoxCanDoX *pBox) = 0; 
    virtual void Visit(IBoxCanDoY *pBox) = 0; 
    virtual void Visit(IBox* pBox) = 0; 
}; 

class IBox // all boxes do these 
{ 
public: 
    virtual void Accept(IVisitor *pVisitor) 
    { 
     pVisitor->Visit(this); 
    } 
}; 

class BoxCanDoY : public IBox 
{ 
public: 
    virtual void Accept(IVisitor *pVisitor) 
    { 
     pVisitor->Visit(this); 
    } 
}; 
class TestVisitor : public IVisitor 
{ 
public: 
    // override visit methods to do tests for each type. 
}; 

void Main() 
{ 
    BoxCanDoY y; 
    TestVisitor v; 
    y.Accept(&v); 
} 
+0

是的,但不要忘記研究許多類型的實現的訪客模式,以便您使用適合您需求的內容。除了這個答案中的一個(從GoF直接出來)之外,還有非循環,合作和層次......可能更多。每個人都有自己的長處和短處,所以在開始使用錯誤之前你最好仔細考慮。 – 2010-07-26 17:06:21

+0

@Noah,好點。理解可用的選項當然不會傷害。但是,我所看到的變化是爲了更好地分離訪問者和受訪者之間的編譯時間依賴關係。測試代碼幾乎總是與測試代碼直接綁定並編譯,所以香草訪問者模式可能就足夠了。 – 2010-07-26 17:30:28

+0

我不明白測試代碼的相關性。測試代碼通常不會使用訪問,因爲您自己測試每個實現。 – 2010-07-26 18:03:59

2

你給的選項中,我會說是b或者d是「最好的」。然而,需要做很多這類事情往往是一個糟糕的設計,或者是一個可以更好地在動態類型語言而不是C++中實現的設計。

+1

動態打字並不能真正解決任何問題。你仍然必須做你的支票,並且經常也必須做你的演員。 dynamic_cast選項提供了所需的動態類型的所有功能。 – 2010-07-26 17:20:44

0

如果您試圖從代碼的或然部分調用這些類中的任何一個,我建議您將該代碼包裝在模板函數中,並以相同的方式命名每個類的方法來實現duck typing,因此您的客戶端代碼看起來像這樣。

template<class box> 
void box_do_xory(box BOX){ 
    BOX.xory(); 
} 
0

對您的問題沒有一般的答案。一切都依賴。我只能說:
- 不要用a),用b)代替
- b)很好,需要最少的代碼,不需要虛擬方法,但dynamic_cast有點慢
- c)is類似於b),但它更快(沒有dynamic_cast),並需要更多的內存
- e)沒有意義,你仍然需要發現,如果你可以調用該方法,所以不會拋出異常
- d) f)(編寫較少的代碼)
-d)e)和f)產生更多垃圾代碼,然後生成更多垃圾代碼,但速度更快,內存消耗更少

1

A和B需要運行時類型識別(RTTI),如果您正在進行大量檢查,則可能會變慢。就我個人而言,我不喜歡「canDoX」方法的解決方案,如果出現這種情況,設計可能需要升級,因爲您暴露的信息與課程無關。

如果你只需要執行X或Y,取決於類,我會去在IBOX一個虛擬的方法,獲得在子類中重寫。

class IBox{ 
    virtual void doThing(); 
} 
class IBoxCanDoX: public IBox{ 
    void doThing() { doX(); } 
    void doX(); 
} 
class IBoxCanDoY: public IBox{ 
    void doThing() { doY(); } 
    void doY(); 
} 

box->doThing(); 

如果該解決方案不適用或者您需要更復雜的邏輯,請查看Visitor設計模式。但請記住,當您定期添加新類時,或者方法更改/添加/刪除(但您的建議替代方案也適用)時,訪問者模式不是非常靈活。

+0

更正:當您添加新課程或更改行爲時,某些*類型的訪問者是僵化的。非循環訪問者實際上很擅長處理這類事情。 – 2010-07-26 17:26:18

+0

toefel,可悲的是一個盒子可以做x和y – pm100 2010-07-26 18:12:55

0

我假設你不僅在這裏使用一種類型的一個對象。

我會列出您正在使用的數據,並嘗試瞭解如何將其放置在內存中以執行數據驅動編程。內存中一個好的佈局應該反映出你將數據存儲在你的類中的方式,以及這些類是如何放在內存中的。一旦你有了基本的設計結構(應該不會超過餐巾紙),我將開始根據你打算對數據進行的操作將對象組織成列表。如果您計劃在子集X中的對象集合{Y}上執行X(),我可能會確保從頭開始創建一個Y靜態數組。如果你想偶爾訪問整個X,可以通過將列表收集到一個動態指針列表(使用std :: vector或您最喜歡的選項)來安排。

我希望這是有道理的,但一旦實施,它提供了易於理解且易於使用的簡單直接解決方案。

0

有一種通用的方法來測試一個類是否支持某個概念,然後執行最合適的代碼。它使用SFINAE黑客。這個例子受到Abrahams和Gurtovoy的「C++模板元編程」書的啓發。功能doIt將使用x方法如果它存在,否則將使用y方法。您也可以擴展CanDo結構來測試其他方法。您可以根據需要測試任意多種方法,只要可以唯一解決超載問題。

#include <iostream> 
#include <boost/config.hpp> 
#include <boost/utility/enable_if.hpp> 

typedef char yes;  // sizeof(yes) == 1 
typedef char (&no)[2]; // sizeof(no) == 2 

template<typename T> 
struct CanDo { 
    template<typename U, void (U::*)()> 
    struct ptr_to_mem {}; 

    template<typename U> 
    static yes testX(ptr_to_mem<U, &U::x>*); 

    template<typename U> 
    static no testX(...); 

    BOOST_STATIC_CONSTANT(bool, value = sizeof(testX<T>(0)) == sizeof(yes)); 
}; 

struct DoX { 
    void x() { std::cout << "doing x...\n"; } 
}; 

struct DoAnotherX { 
    void x() { std::cout << "doing another x...\n"; } 
}; 

struct DoY { 
    void y() { std::cout << "doing y...\n"; } 
}; 

struct DoAnotherY { 
    void y() { std::cout << "doing another y...\n"; } 
}; 

template <typename Action> 
typename boost::enable_if<CanDo<Action> >::type 
doIt(Action* a) { 
    a->x(); 
} 

template <typename Action> 
typename boost::disable_if<CanDo<Action> >::type 
doIt(Action* a) { 
    a->y(); 
} 

int main() { 

    DoX   doX; 
    DoAnotherX doAnotherX; 
    DoY   doY; 
    DoAnotherY doAnotherY; 

    doIt(&doX); 
    doIt(&doAnotherX); 
    doIt(&doY); 
    doIt(&doAnotherY); 
}