QT C++ 虚函数的使用案例:创造发布-订阅模式来实现类与类之间通讯
·
前情提要
不理解什么是虚函数的,可以看我之前写的文章
背景
问题:有大量的类,需要相互发送消息,使用信号与槽,会增加代码量,难以维护。
解决方法:使用虚函数,来实现注册订阅的方式来发送消息,这样以来会极大的减小代码量
注册订阅使用方法
注册: 每一个类,在构造函数中,注册自己的类在总线类中(相当于登记自家的门牌号)。
发送:每一个类,在发送消息的时候,只需要填:要给那个类发 以及:消息 即可。
类关系图

类介绍
ReceiverClass
是一个抽象基类,用于定义消息接收的统一接口
class ReceiverClass : public QObject {
Q_OBJECT
public:
/**
* @brief ReceiverClass 构造函数
* @param parent 父对象指针,默认为 nullptr
*
* ReceiverClass 是一个抽象基类,用于定义消息接收的统一接口。
* 子类需要继承该类并实现 receiveMessage 方法,以便接收消息。
*/
explicit ReceiverClass(QObject* parent = nullptr) : QObject(parent) {}
/**
* @brief 接收消息的虚方法
* @param message 接收到的消息,类型为 QString
*
* 该方法是一个纯虚函数,子类必须实现此方法,用于处理接收到的消息。
*/
virtual void receiveMessage(const QString& message) = 0;
};
MessageBus
是一个消息总线类
MessageBus.h
#ifndef MESSAGEBUS_H
#define MESSAGEBUS_H
#include <QObject>
#include <QHash>
#include <QList>
#include <QString>
#include <QMetaObject>
#include <QThread>
class ReceiverClass; // 接收消息的基类
/**
* @brief 消息总线类
*
* MessageBus 是一个基于单例模式的类,负责管理消息的发送与接收。
* 通过注册接收者类,可以实现基于消息类型的解耦通信。
*/
class MessageBus : public QObject {
Q_OBJECT
public:
/**
* @brief 获取 MessageBus 单例实例
* @return 指向单例实例的指针
*
* 使用单例模式确保整个应用程序中只有一个消息总线实例。
*/
static MessageBus* instance();
/**
* @brief 注册接收者类
* @param messageType 消息类型,字符串表示
* @param receiver 指向接收者类的指针
*
* 将接收者对象注册到消息总线中,以便接收指定类型的消息。
* 一个消息类型可以注册多个接收者。
*/
void registerReceiver(const QString& messageType, ReceiverClass* receiver);
/**
* @brief 发送消息
* @param messageType 消息类型,字符串表示
* @param message 消息内容
*
* 将消息发送到所有注册了对应消息类型的接收者对象。
* 消息类型与接收者的映射关系由 registerReceiver 方法维护。
*/
void sendMessage(const QString& messageType, const QString& message);
private:
/**
* @brief 消息注册表
*
* 使用 QHash 容器管理消息类型与接收者列表的映射关系。
* Key 为消息类型,Value 为接收者对象指针列表。
*/
QHash<QString, QList<ReceiverClass*>> messageRegistry;
/**
* @brief 私有构造函数
*
* 构造函数被设为私有,以确保不能直接实例化 MessageBus。
* 单例模式需要通过 instance() 方法访问。
*/
MessageBus() = default;
};
#endif // MESSAGEBUS_H
MessageBus.cpp
#include "MessageBus.h"
#include "ReceiverClass.h"
/**
* @brief 获取 MessageBus 单例实例
* @return 指向单例实例的指针
*
* 使用局部静态变量实现单例模式。
* 线程安全,确保整个程序中只会创建一个 MessageBus 实例。
*/
MessageBus* MessageBus::instance() {
static MessageBus instance; // 静态局部变量,生命周期贯穿程序运行期
return &instance;
}
/**
* @brief 注册消息接收者
* @param messageType 消息类型,字符串表示
* @param receiver 指向接收者类的指针
*
* 将接收者对象注册到消息注册表中。如果指定消息类型尚未存在,
* 则会为其创建一个新的接收者列表,并将该接收者添加进去。
*/
void MessageBus::registerReceiver(const QString& messageType, ReceiverClass* receiver) {
if (!messageRegistry.contains(messageType)) {
// 如果消息类型尚未注册,创建一个新的接收者列表
messageRegistry[messageType] = QList<ReceiverClass*>();
}
// 将接收者添加到对应的消息类型列表中
messageRegistry[messageType].append(receiver);
}
/**
* @brief 发送消息
* @param messageType 消息类型,字符串表示
* @param message 消息内容
*
* 根据消息类型查找所有已注册的接收者,并将消息发送给他们。
* 如果接收者与消息发送者在同一线程中,直接调用接收方法。
* 如果接收者在不同线程,则使用 Qt 的 invokeMethod 通过事件队列
* 进行线程间通信。
*/
void MessageBus::sendMessage(const QString& messageType, const QString& message) {
if (messageRegistry.contains(messageType)) {
// 遍历所有注册在该消息类型下的接收者
for (ReceiverClass* receiver : messageRegistry[messageType]) {
if (receiver) {
// 如果接收者和当前线程一致,直接调用接收方法
if (receiver->thread() == QThread::currentThread()) {
receiver->receiveMessage(message);
} else {
// 如果不在同一线程,使用 Qt 的 invokeMethod 实现线程间通信
QMetaObject::invokeMethod(receiver, "receiveMessage", Qt::QueuedConnection, Q_ARG(QString, message));
}
}
}
}
}
ClassA
用于注册消息与发送消息
ClassA.h
#ifndef CLASS_A_H
#define CLASS_A_H
#include <QObject>
#include "ReceiverClass.h"
#include "MessageBus.h"
#include <QDebug>
/**
* @brief Class_A 类
*
* Class_A 继承自 ReceiverClass,表示一个具体的消息接收者类。
* 它实现了消息接收功能,并能够通过消息总线发送消息。
*/
class Class_A : public ReceiverClass {
Q_OBJECT
public:
/**
* @brief Class_A 构造函数
*
* 初始化 Class_A 对象,并注册到消息总线中(可在实现部分完成)。
*/
Class_A();
/**
* @brief 接收消息并显示
* @param message 接收到的消息,类型为 QString
*
* 实现了 ReceiverClass 的纯虚方法,用于处理接收到的消息。
* 此处通过 qDebug 输出消息内容。
*/
void receiveMessage(const QString& message) override;
/**
* @brief 发送消息
*
* 示例方法,用于通过消息总线发送消息。
* 消息类型和内容可以在实现部分定义。
*/
void sendMsg();
};
#endif // CLASS_A_H
ClassA.cpp
#include "Class_A.h"
/**
* @brief Class_A 构造函数
*
* 初始化 Class_A 对象,并将自身注册到消息总线中。
* 注册的消息类型为 "MSG_To_A",以便接收此类型的消息。
*/
Class_A::Class_A() {
// 将当前对象注册为消息接收者,接收 "MSG_To_A" 类型的消息
MessageBus::instance()->registerReceiver("MSG_To_A", this);
}
/**
* @brief 接收消息的实现
* @param message 接收到的消息内容,类型为 QString
*
* 实现了 ReceiverClass 的纯虚方法,用于接收并处理消息。
* 此处通过 qDebug 将消息内容打印到调试控制台。
*/
void Class_A::receiveMessage(const QString &message) {
qDebug() << "Class_A received message:" << message;
}
/**
* @brief 发送消息的实现
*
* 使用消息总线发送一条消息。
* 消息类型为 "MSG_To_B",消息内容为 "这是A发的消息给B的"。
*/
void Class_A::sendMsg() {
MessageBus::instance()->sendMessage("MSG_To_B", "这是A发的消息给B的");
}
ClassB
用于注册消息与发送消息
ClassB.h
#ifndef CLASS_B_H
#define CLASS_B_H
#include <QObject>
#include "ReceiverClass.h"
#include "MessageBus.h"
#include <QDebug>
/**
* @brief Class_B 类
*
* Class_B 是一个继承自 ReceiverClass 的具体消息接收者类。
* 它实现了消息的接收功能,并提供发送消息的功能。
*/
class Class_B : public ReceiverClass {
Q_OBJECT
public:
/**
* @brief Class_B 构造函数
*
* 初始化 Class_B 对象,并注册到消息总线中。
*/
Class_B();
/**
* @brief 接收消息并显示
* @param message 接收到的消息,类型为 QString
*
* 实现 ReceiverClass 的纯虚方法,用于接收并处理指定类型的消息。
*/
void receiveMessage(const QString& message) override;
/**
* @brief 发送消息
*
* 示例方法,用于通过消息总线发送消息。
* 消息类型和内容在实现部分定义。
*/
void sendMsg();
};
#endif // CLASS_B_H
ClassB.cpp
#include "Class_B.h"
/**
* @brief Class_B 构造函数
*
* 初始化 Class_B 对象,并将自身注册到消息总线中。
* 注册的消息类型为 "MSG_To_B",以便接收此类型的消息。
*/
Class_B::Class_B() {
// 将当前对象注册为消息接收者,接收 "MSG_To_B" 类型的消息
MessageBus::instance()->registerReceiver("MSG_To_B", this);
}
/**
* @brief 接收消息的实现
* @param message 接收到的消息内容,类型为 QString
*
* 实现了 ReceiverClass 的纯虚方法,用于接收并处理消息。
* 此处通过 qDebug 将消息内容打印到调试控制台。
*/
void Class_B::receiveMessage(const QString &message) {
qDebug() << "Class_B received message:" << message;
}
/**
* @brief 发送消息的实现
*
* 使用消息总线发送一条消息。
* 消息类型为 "MSG_To_A",消息内容为 "这是B发的消息给A的"。
*/
void Class_B::sendMsg() {
MessageBus::instance()->sendMessage("MSG_To_A", "这是B发的消息给A的");
}
为什么需要虚函数来做
-
实现接口多态:
- 在
ReceiverClass中定义了一个纯虚函数receiveMessage,表示所有的接收者必须实现该接口。 - 通过这种方式,消息总线
MessageBus不需要关心接收者的具体类型,只需要调用基类的接口就能触发对应的消息处理逻辑。
- 在
-
支持不同的消息处理逻辑:
- 每个接收者可能需要根据业务逻辑,对消息进行完全不同的处理。
- 使用虚函数,允许子类重写
receiveMessage方法,实现各自的消息处理逻辑。
-
解耦发送者与接收者:
- 通过虚函数,消息的发送者完全不需要了解接收者的内部实现。
- 只需通过基类指针调用接收方法即可,符合 依赖倒置原则。
-
扩展性:
- 如果以后需要添加新的接收者,只需创建一个继承自
ReceiverClass的类,并实现receiveMessage。 - 不需要修改现有的代码,符合 开闭原则。
- 如果以后需要添加新的接收者,只需创建一个继承自
总结
虚函数的主要用途是为通信框架提供一种 动态多态机制:
- 定义统一的消息接收接口,保证各接收者遵循相同的调用方式。
- 让不同接收者能以多态的方式实现各自的消息处理逻辑。
- 解耦发送者与接收者的具体实现,提升系统的扩展性与可维护性。
这也是使用虚函数实现通信框架的最佳实践之一。
更多推荐


所有评论(0)