检查对象类型真的总是设计不好吗?

时间:2015-05-06 10:59:46

标签: c++ types

我有一些文本行的来源,每行都是一条消息,代表某种类型的对象。我正在为这些行创建一个解析器,它应该将文本行作为输入并将准备使用的对象作为输出。所以我创建了以下类的层次结构:

class Message
{
public:
    virtual ~Message(){};
};

class ObjectTypeA : public Message
{/*...*/};
class ObjectTypeB : public Message
{/*...*/};
class ObjectTypeC : public Message
{/*...*/};

以及它是如何使用的:

std::shared_ptr<Message> parseLine(std::string& line);

void doWork()
{
    std::string line;
    while(getLine(line))
    {
        std::shared_ptr<Message> object=parseLine(line);
        if(dynamic_cast<ObjectTypeA*>(object.get()))
            doSomethingA(*static_cast<ObjectTypeA*>(object.get()));
        else if(dynamic_cast<ObjectTypeB*>(object.get()))
            doCompletelyUnrelatedProcessing(*static_cast<ObjectTypeB*>(object.get()));
        else if(dynamic_cast<ObjectTypeC*>(object.get()))
            doSomethingEvenMoreDifferent(*static_cast<ObjectTypeC*>(object.get()));
    }
}

此处解析器将是一个库函数,并且对象事先不知道它们将如何处理。因此,我无法将处理代码放到Message实现的虚函数中。

但是this question中的许多答案都说如果需要检查对象的类型,那就是设计糟糕的迹象。但我似乎无法看到这里有什么不好。有没有更好的方法来组织解决方案?

3 个答案:

答案 0 :(得分:3)

您真的在询问有关错误设计的意见。这是我的:

你的糟糕的设计,因为你试图在另一个应该由子类处理的类中做某事,因为那是多态的含义。

你的母班应该有一个

virtual void do_stuff_that_is_specific_to_the_subclass(...) = 0;

方法,您将在子类中实现。

  

此处解析器将是一个库函数,并且对象不会事先知道它们将如何处理。因此,我无法将处理代码放到Message实现的虚函数中。

为什么不呢?你应该只是一个

virtual void do_stuff_that_is_specific_to_the_subclass(parser&, ...) = 0;

为每个子类使用不同解析器的方法。没有理由说你在if/else条款中可以做的事情不能在子类中完成,除非它打破封装,我怀疑,因为你和我的唯一原因#39;有了这些对象,你想为不同的行做不同的具体事情。

答案 1 :(得分:3)

首先,它不会总是设计糟糕的迹象。 &#34; soft&#34;中的绝对数量很少。喜欢&#34;好&#34;或者&#34;坏&#34;设计。然而,由于以下一个或多个原因,它通常表明不同的方法是可取的:可扩展性,易维护性,熟悉性等。

在您的特定情况下:在没有类型切换或膨胀/污染类的接口的情况下进行任意类特定处理的标准方法之一是使用Visitor pattern。您可以创建一个通用的MessageVisitor接口,教导Message子类调用它,并在需要处理它们的任何地方实现它:

class MessageVisitor;

class Message
{
public:
    virtual ~Message(){};

    virtual void accept(MessageVisitor &visitor) = 0;
};

class ObjectTypeA : public Message
{
  void accept(MessageVisitor &visitor) override
  { visitor.visit(*this); }
/*...*/
};

class ObjectTypeB : public Message
{
  void accept(MessageVisitor &visitor) override
  { visitor.visit(*this); }
/*...*/
};

class ObjectTypeC : public Message
{
  void accept(MessageVisitor &visitor) override
  { visitor.visit(*this); }
/*...*/
};

class MessageVisitor
{
public:
  virtual void visit(ObjectTypeA &subject) {}

  virtual void visit(ObjectTypeB &subject) {}

  virtual void visit(ObjectTypeC &subject) {}
};

然后你会像这样使用它:

void doWork()
{
    struct DoWorkMessageVisitor : MessageVisitor
    {
      void visit(ObjectTypeA &subject) override { doSomethingA(subject); }

      void visit(ObjectTypeB &subject) override { doSomethingB(subject); }

      void visit(ObjectTypeC &subject) override { doSomethingC(subject); }
    };
    std::string line;
    while(getLine(line))
    {
        std::shared_ptr<Message> object=parseLine(line);
        DoWorkMessageVisitor v;
        object->accept(v);
    }
}

根据需要,可以根据const重载等进行自定义。

请注意,accept无法在基类中实现,因为在调用*this时需要正确类型的visit是类型开关已经移动&#34;。

的地方

另一种方法是使visit中的MessageVisitor函数为纯虚拟而不是空。然后,如果您需要添加新的消息类型,它将自动强制您更新发生此类特定处理的所有位置。

答案 2 :(得分:0)

doSomethingAdoCompletelyUnrelatedProcessingdoSomethingEvenMoreDifferent可能只是覆盖Message类的纯虚函数。在您的情况下,作为一种设计解决方案会更有效,更好。