[英]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.