2010-08-04 113 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;