2010-08-04 85 views
6

我有一些像在頭設計模式重構switch語句

class MsgBase 
{ 
    public: 
    unsigned int getMsgType() const { return type_; } 
    ... 
    private: 
    enum Types { MSG_DERIVED_1, MSG_DERIVED_2, ... MSG_DERIVED_N }; 
    unsigned int type_; 
    ... 
}; 

class MsgDerived1 : public MsgBase { ... }; 
class MsgDerived2 : public MsgBase { ... }; 
... 
class MsgDerivedN : public MsgBase { ... }; 

以下,並用作

MsgBase msgHeader; 
// peeks into the input stream to grab the 
// base class that has the derived message type 
// non-destructively 
inputStream.deserializePeek(msgHeader); 
unsigned int msgType = msgHeader.getMsgType(); 

MsgDerived1 msgDerived1; 
MsgDerived2 msgDerived2; 
... 
MsgDerivedN msgDerivedN; 

switch(msgType) 
{ 
    case MSG_DERIVED_1: 
    // fills out msgDerived1 from the inputStream 
    // destructively 
    inputStream.deserialize(msgDerived1); 
    /* do MsgDerived1 processing */ 
    break; 
    case MSG_DERIVED_2: 
    inputStream.deserialize(msgDerived2); 
    /* do MsgDerived1 processing */ 
    break; 
    ... 
    case MSG_DERIVED_N: 
    inputStream.deserialize(msgDerivedN); 
    /* do MsgDerived1 processing */ 
    break; 
} 

這似乎是這將是相當普遍的良好形勢的類型適合重構。應用設計模式(或基本的C++語言特性重新設計)來重構此代碼的最佳方式是什麼?

我讀過Command模式通常用於重構switch語句,但似乎只適用於在執行任務的算法之間進行選擇。這是一個工廠還是抽象工廠模式適用的地方(我對此不是很熟悉)?雙派遣?

我試圖忽略儘可能多的無關緊要的上下文,但如果我錯過了一些重要的事情,請讓我知道,我將編輯以包含它。此外,我找不到任何類似的東西,但如果這是重複的,只是將我重定向到適當的SO問題。

+0

訪客模式很適合替換像這樣的開關。 – neoneye 2010-08-04 16:01:55

+0

@neoneye:訪客模式基於兩個現有對象的動態類型實現雙重調度。在這種情況下,我們需要確定要創建哪種類型的對象。 – 2010-08-04 16:42:26

回答

2

拉類型和type_出MsgBase,他們不屬於那裏。

如果你想得到完全的幻想,把所有的派生類型與工廠一起註冊,以及工廠用來知道要做什麼的令牌(例如'類型')。然後,工廠在其表中反序列化上查找該標記,並創建正確的消息。

class DerivedMessage : public Message 
{ 
public: 
    static Message* Create(Stream&); 
    bool Serialize(Stream&); 

private: 
    static bool isRegistered; 
}; 

// sure, turn this into a macro, use a singleton, whatever you like 
bool DerivedMessage::isRegistered = 
     g_messageFactory.Register(Hash("DerivedMessage"), DerivedMessage::Create); 

等創建靜態方法分配新DerivedMessage和反序列化,Serialize方法寫入令牌(在這種情況下,Hash("DerivedMessage"))和再序列本身。其中一個可能應該測試isRegistered,以便它不會被鏈接器死掉。 (值得注意的是,這種方法並不需要枚舉或其他任何可以存在的「靜態列表」,目前我無法想到另一種在某種程度上不需要循環引用的方法。 )

5

您可以使用Factory Method模式,根據您從流中查看的值創建基類(派生類)的正確實現。

+4

工廠方法,如您提供的維基百科鏈接所解釋的,仍然有一個'switch'語句,它只是隱藏在工廠方法中。 – 2010-08-04 16:15:11

+0

是的,這是真的。關鍵是它是隱藏的。在某些時候,你將不得不決定你想要創建什麼。 – 2010-08-04 16:17:36

+1

它還消除了基類對派生實現有任何知識的需求(通過將創建委託給類結構之外),並且通常通過接口原則實現良好的設計(因爲它應該返回一個指向基類的指針) – 2010-08-04 16:25:15

1

對於一個基類來說,有關派生類的知識通常是一個壞主意,所以重新設計絕對是有序的。您已經注意到,工廠模式可能就是您想要的。

3

該開關並不全是壞的。這是實施工廠模式的一種方式。它很容易測試,它可以很容易地理解整個可用對象的範圍,這對覆蓋測試很有用。

另一種技術是在你的枚舉類型和工廠之間建立一個映射,以便從數據流中創建特定的對象。這將編譯時開關轉換爲運行時查找。該映射可以在運行時建立,從而可以添加新類型而不用重新編譯所有內容。

// You'll have multiple Factories, all using this signature. 
typedef MsgBase *(*Factory)(StreamType &); 

// For example: 
MsgBase *CreateDerived1(StreamType &inputStream) { 
    MsgDerived1 *ptr = new MsgDerived1; 
    inputStream.deserialize(ptr); 
    return ptr; 
} 

std::map<Types, Factory> knownTypes; 
knownTypes[MSG_DERIVED_1] = CreateDerived1; 

// Then, given the type, you can instantiate the correct object: 
MsgBase *object = (*knownTypes[type])(inputStream); 

... 

delete object;