Я новичок в C ++ и пришел к точке, где я генерирую накладные расходы с классами. У меня есть QTcpSocket, я читаю из него сообщения и создаю объекты, например MessageJoin, MessagePart, MessageUserData и т. Д. Я отправляю эти объекты своему клиенту и отображаю их (+ обновляю пользовательский интерфейс).
Теперь вот моя проблема. Я протестировал несколько методов проектирования, но все они не так хороши:
Для лучшего понимания я добавил свою версию 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
}
}
Это выглядит очень похоже на проблема выражения и 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 или повышающие сигналы / слоты, чтобы вы могли иметь несколько обработчиков для одного сообщения, но основная идея та же.
Лучшим дизайном может быть наличие абстрактной виртуальной функции в 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
это делает это.
Основная идея здесь состоит в том, чтобы попытаться свести к минимуму связь и зависимости между вашими классами. Чем меньше связи, тем легче будет изменить один или другой, или добавить новые классы сообщений, или даже полностью переписать один или другой из задействованных классов, не затрагивая другие классы. Вот почему мы разделяем код на интерфейс и реализацию и скрытие данных.
шаблон посетителя может быть хорошо подходит то есть
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
}
}