我是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
}
}
答案 0 :(得分:2)
更好的设计可能是在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
来执行此操作。
这里的主要想法是尽量减少类之间的耦合和依赖关系。它的耦合越少,修改一个或另一个就越容易,或者添加新的消息类,甚至完全重写一个或另一个所涉及的类而不影响其他类。这就是我们将代码分为接口,实现和数据隐藏的原因。
答案 1 :(得分:2)
这看起来非常类似于the expression problem和AFAIK,如果要添加新消息和处理它们的新方法,则无法避免强制转换。然而,对于必要的运行时间来说,使用更令人愉悦的包装并不难。只需使用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或增强信号/插槽,这样您就可以为单个消息提供多个处理程序,但核心思想是相同的。
答案 2 :(得分:1)
visitor pattern可能是一个不错的选择,即
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
}
}