【问题标题】:Design pattern to refactor switch statement重构switch语句的设计模式
【发布时间】:2011-03-25 08:36:34
【问题描述】:

我在标题中有如下内容

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++ 语言功能重新设计)重构此代码的最佳方式是什么?

我已经读到命令模式通常用于重构 switch 语句,但这似乎只适用于在算法之间进行选择以执行任务。这是工厂或抽象工厂模式适用的地方吗(我也不是很熟悉)?双重派送?

我已尝试尽可能多地省略无关紧要的上下文,但如果我错过了重要的内容,请告诉我,我会进行编辑以包含它。另外,我找不到任何类似的东西,但如果这是重复的,只需将我重定向到适当的 SO 问题。

【问题讨论】:

  • 访问者模式很适合替换像这样的开关。
  • @neoneye:访问者模式基于两个现有对象的动态类型实现了双分派。在这种情况下,我们需要确定要创建哪种类型的对象。

标签: c++ design-patterns refactoring


【解决方案1】:

从 MsgBase 中拉出 Types 和 type_,它们不属于那里。

如果您想完全看中,请向工厂注册所有派生类型以及工厂将用来知道要做什么的令牌(例如“类型”)。然后,工厂在其表中反序列化时查找该令牌,并创建正确的消息。

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 静态方法分配一个新的 DerivedMessage 并对其进行反序列化,Serialize 方法写入令牌(在本例中为Hash("DerivedMessage")),然后对其进行序列化。其中之一可能应该测试 isRegistered 以免被链接器完全剥离。

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

【讨论】:

    【解决方案2】:

    这个开关并不全是坏事。这是实现工厂模式的一种方式。它易于测试,便于理解整个可用对象范围,并且非常适合覆盖测试。

    另一种技术是在枚举类型和工厂之间建立映射,以从数据流中生成特定对象。这会将编译时开关转换为运行时查找。映射可以在运行时构建,从而可以添加新类型而无需重新编译所有内容。

    // 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;
    

    【讨论】:

      【解决方案3】:

      对于基类来说,了解派生类的知识通常是个坏主意,因此重新设计肯定是有必要的。正如您已经指出的那样,工厂模式可能就是您想要的。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 工厂方法,正如您提供的维基百科链接所解释的那样,仍然有一个switch 声明,它只是隐藏在工厂方法中。
        • 是的,这是真的。关键是它是隐藏的。在某些时候,您将不得不决定要创建什么。
        • 它还消除了基类对派生实现的任何了解(通过在类结构之外委托创建),并且通常通过接口原则强制执行良好的设计(因为它应该返回指向基类的指针)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-06-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多