2

我是 C++ 的新手,我遇到了一个问题,即我会在类中产生开销。我有一个 QTcpSocket 并从中读取消息并创建对象,例如 MessageJoin、MessagePart、MessageUserData 等。我将这些对象发送到我的客户端并显示它们(+ 进行一些 UI 更新)。

现在我的问题来了。我测试了一些设计技术,但都不是很好:

  • 将信号/槽连接中的消息对象的每个参数传递给客户端 - 开销很小但不是那么好看
  • 为每个 Message-Type(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
    }
}
4

3 回答 3

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
    }
}
于 2013-07-09T11:56:37.203 回答
2

更好的设计可能是在Message类中有一个抽象的虚函数,被调用processonReceive类似,子类实现这个函数。然后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

这里最大的想法是尽量减少类之间的耦合和依赖关系。耦合越少,修改一个或另一个,或添加新的消息类,甚至完全重写一个或另一个所涉及的类,而不影响其他类就越容易。这就是我们将代码拆分为接口和实现以及数据隐藏的原因。

于 2013-07-09T11:47:01.983 回答
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 或提升信号/插槽,以便您可以为一条消息拥有多个处理程序,但核心思想是相同的。

于 2013-07-09T13:32:03.307 回答