Избегайте dynamic_cast с производными классами (класс Cast Derived)

Я новичок в C ++ и пришел к точке, где я генерирую накладные расходы с классами. У меня есть QTcpSocket, я читаю из него сообщения и создаю объекты, например MessageJoin, MessagePart, MessageUserData и т. Д. Я отправляю эти объекты своему клиенту и отображаю их (+ обновляю пользовательский интерфейс).

Теперь вот моя проблема. Я протестировал несколько методов проектирования, но все они не так хороши:

  • Передайте каждый параметр объекта сообщения в соединении сигнал / слот клиенту — небольшие накладные расходы, но не такие красивые
  • Создайте метод для каждого типа сообщения (messageJoinReceived, messageNoticeReceived и т. Д.)
  • Создайте один метод и используйте dynamic_cast для приведения каждого класса и проверки его

Для лучшего понимания я добавил свою версию dynamic_cast. Как уже говорилось, код выглядит уродливо и непригодным для использования. Мои вопросы:

  • Есть ли лучший способ сделать это с (a) dynamic_cast
  • Есть ли другой способ (например, шаблон проектирования), чтобы решить такую ​​проблему? может быть, добавить метод в классах и вернуть тип или что-то вроде этого
  • Я читал о шаблоне посетителя. Этот шаблон только для динамических типов объектов в методах Getter / Setter?

Несколько примечаний

  • Я могу использовать RTTI
  • Скорость не имеет большого значения. Чистый и понятный код важнее
  • Я использую Qt и имею возможность использовать qobject_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
}
}

2

Решение

Это выглядит очень похоже на проблема выражения и 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

Другие решения

Лучшим дизайном может быть наличие абстрактной виртуальной функции в 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 это делает это.

Основная идея здесь состоит в том, чтобы попытаться свести к минимуму связь и зависимости между вашими классами. Чем меньше связи, тем легче будет изменить один или другой, или добавить новые классы сообщений, или даже полностью переписать один или другой из задействованных классов, не затрагивая другие классы. Вот почему мы разделяем код на интерфейс и реализацию и скрытие данных.

2

шаблон посетителя может быть хорошо подходит то есть

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
}
}
1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector