重构switch语句的设计模式

时间:2010-08-04 15:59:58

标签: c++ design-patterns refactoring

我在标题

中有以下内容
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问题。

4 个答案:

答案 0 :(得分:5)

您可以使用Factory Method模式,根据您从流中查看的值,创建基类(派生类)的正确实现。

答案 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;

答案 2 :(得分:2)

拉出类型并输出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);

等。 Create static方法分配一个新的DerivedMessage并对其进行反序列化,Serialize方法写入令牌(在本例中为Hash("DerivedMessage")),然后自行序列化。其中一个应该测试isRegistered,以便它不会被链接器剥离。

(值得注意的是,这种方法不需要枚举或其他“可能存在的所有东西的静态列表”。此时我想不出另一种在某种程度上不需要循环引用的方法。)

答案 3 :(得分:1)

对于基类来说,掌握派生类的知识通常是一个坏主意,所以重新设计肯定是有序的。工厂模式可能就像你已经注意到的那样。