我有一些文本行的来源,每行都是一条消息,代表某种类型的对象。我正在为这些行创建一个解析器,它应该将文本行作为输入并将准备使用的对象作为输出。所以我创建了以下类的层次结构:
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中的许多答案都说如果需要检查对象的类型,那就是设计糟糕的迹象。但我似乎无法看到这里有什么不好。有没有更好的方法来组织解决方案?
答案 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)
doSomethingA
,doCompletelyUnrelatedProcessing
和doSomethingEvenMoreDifferent
可能只是覆盖Message类的纯虚函数。在您的情况下,作为一种设计解决方案会更有效,更好。