[英]Avoid dynamic_cast with derived classes (Cast Derived class)
我是C ++的新手,并且到了某种程度,我在类上产生了开销。 我有一个QTcpSocket并从中读取消息并创建对象,例如MessageJoin,MessagePart,MessageUserData等。我将这些对象发送到我的客户端并显示它们(+做一些UI更新)。
现在是我的问题了。 我测试了一些设计技术,但它们都不是很好:
为了更好地理解,我添加了dynamic_cast版本。 可以这么说,该代码看起来难看且无法使用。 我的问题是 :
一些注意事项
这是我的代码( Pastebin-Link ):
// Default class - contains the complete message (untouched)
class Message
{
public:
QString virtual getRawMessage() { return dataRawMessage; }
protected:
QString dataRawMessage;
};
// Join class - cointains the name of the joined user and the channel
class MessageJoin : public Message
{
public:
MessageJoin(const QString &rawmessage, const QString &channel, const QString &user)
{
dataRawMessage = rawmessage;
dataChannel = channel;
dataUser = user;
}
QString getChannel() { return dataChannel; }
QString getUser(){ return dataUser; }
private:
QString dataChannel;
QString dataUser;
};
// Notice class - contains a notification message
class MessageNotice : public Message
{
public:
MessageNotice(const QString &rawmessage, const QString &text)
{
dataRawMessage = rawmessage;
dataText = text;
}
QString getText() { return dataText;}
private:
QString dataText;
};
// Client code - print message and update UI
void Client::messageReceived(Message *message)
{
if(message)
{
MessageJoin *messagejoin;
MessagePart *messagepart;
MessageNotice *messagenotice;
if((messagejoin = dynamic_cast<MessageJoin *>(message)) != 0)
{
qDebug() << messagejoin->getUser() << " joined " << messagejoin->getChannel();
// Update UI: Add user
}
else if((messagenotice = dynamic_cast<MessageNotice *>(message)) != 0)
{
qDebug() << messagenotice->getText();
// Update UI: Display message
}
else
{
qDebug() << "Cannot cast message object";
}
delete message; // Message was allocated in the library and is not used anymore
}
}
更好的设计可能是在Message
类中有一个抽象的虚函数,称为process
或onReceive
或类似函数,子类实现此函数。 然后在Client::messageReceived
只需调用此函数:
message->onReceive(...);
无需使用dynamic_cast
。
我还建议您研究智能指针,例如std::unique_ptr
。
如果您在Client
类中具有消息处理功能所需的私有数据,那么可以采用多种方法来解决此问题:
最简单的方法是在客户端中使用普通的“ getter”函数:
class Client { public: const QList<QString>& getList() const { return listContainingUiRelatedStuff; } // Add non-const version if you need to modify the list };
如果您只想在示例中将项目添加到列表中,请为此添加一个函数:
void addStringToList(const QString& str) { listContainingUiRelatedStuff.push_back(str); }
还是非推荐的变体,请使Client
在所有消息类别中成为friend
。
第二种是我的建议。 例如,如果您有所有已连接客户端的列表,并想向所有客户端发送消息,则创建一个功能sendAll
来执行此操作。
这里的主要想法是尝试最小化类之间的耦合和依赖性。 耦合越少,修改一个或另一个,或添加新的消息类,甚至完全重写其中一个或另一个涉及的类,而不影响其他类,就越容易。 这就是为什么我们将代码分为接口,实现和数据隐藏的原因。
这看起来与表达问题和AFAIK非常相似,如果要添加新消息和新方法来处理它们,则无法避免强制转换。 但是,对必要的运行时内容进行更多令人愉悦的包装并不难。 只需使用typeid
创建从消息类型到相应处理程序的typeid
。
#include <functional>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
typedef std::function<void(Message *)> handler_t;
typedef std::unordered_map<
std::type_index,
handler_t> handlers_map_t;
template <class T, class HandlerType>
handler_t make_handler(HandlerType handler)
{
return [=] (Message *message) { handler(static_cast<T *>(message)); };
}
template <class T, class HandlerType>
void register_handler(
handlers_map_t &handlers_map,
HandlerType handler)
{
handlers_map[typeid(T)] = make_handler<T>(handler);
}
void handle(handlers_map_t const &handlers_map, Base *message)
{
handlers_map_t::const_iterator i = handlers_map.find(typeid(*message));
if (i != handlers_map.end())
{
(i->second)(message);
}
else
{
qDebug() << "Cannot handle message object";
}
}
然后注册特定消息类型的处理程序:
handlers_map_t handlers_map;
register_handler<MessageJoin>(
handlers_map,
[] (MessageJoin *message)
{
qDebug() << message->getUser() << " joined " << message->getChannel();
// Update UI: Add user
});
register_handler<MessageNotice>(
handlers_map,
[] (MessageNotice *message)
{
qDebug() << message->getText();
// Update UI: Display message
});
现在您可以处理消息:
// simple test
Message* messages[] =
{
new MessageJoin(...),
new MessageNotice(...),
new MessageNotice(...),
new MessagePart(...),
};
for (auto m: messages)
{
handle(handlers_map, m);
delete m;
}
当然,您可能需要进行一些改进,例如将处理程序内容包装到可重用的类中,使用QT或增强信号/插槽,以便您可以为一条消息使用多个处理程序,但是核心思想是相同的。
访客模式可能很合适,即
class Message
{
public:
QString virtual getRawMessage() { return dataRawMessage; }
virtual void accept(Client& visitor) = 0;
protected:
QString dataRawMessage;
};
// Join class - cointains the name of the joined user and the channel
class MessageJoin : public Message
{
public:
MessageJoin(const QString &rawmessage, const QString &channel, const QString &user)
{
dataRawMessage = rawmessage;
dataChannel = channel;
dataUser = user;
}
QString getChannel() { return dataChannel; }
QString getUser(){ return dataUser; }
void accept(Client& visitor) override
{
visitor.visit(*this);
}
private:
QString dataChannel;
QString dataUser;
};
// Notice class - contains a notification message
class MessageNotice : public Message
{
public:
MessageNotice(const QString &rawmessage, const QString &text)
{
dataRawMessage = rawmessage;
dataText = text;
}
QString getText() { return dataText;}
void accept(Client& visitor) override
{
visitor.visit(*this);
}
private:
QString dataText;
};
void Client::visit(MessageJoin& msg)
{
qDebug() << msg.getUser() << " joined " << msg.getChannel();
// Update UI: Add user
}
void Client::visit(MessageNotice& msg)
{
qDebug() << msg.getText();
// Update UI: Display message
}
// Client code - print message and update UI
void Client::messageReceived(Message *message)
{
if(message)
{
message->visit(this);
delete message; // Message was allocated in the library and is not used anymore
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.