上位机开发过程中的设计模式体会(2):观察者模式和Qt信号槽机制
基本信息
观察者模式和Qt信号槽机制都是用于实现对象间通信的设计模式,但它们在实现方式和应用场景上有显著区别。
我的个人理解是:signal/slot绑定会直接将emit signal时的信息send给其他connect的对象,观察者模式是将观察者作为成员变量加入到信息发送者内部,通过notify函数逐一通知对象;
联系
-
解耦目标
两者都旨在减少对象间的直接依赖,实现松耦合。观察者通过抽象接口解耦,信号槽通过元对象系统(Meta-Object System)解耦。 -
一对多通信
均支持一个对象(被观察者/信号发送者)通知多个其他对象(观察者/槽函数)。 -
事件驱动
常用于响应事件(如用户输入、数据变更),触发后续操作。
区别
维度 | 观察者模式 | Qt信号槽 |
---|---|---|
实现方式 | 基于抽象接口(Observer/Observable),需手动管理观察者列表。 | 基于元对象系统和moc (无需手动管理,通过QObject 自动处理)。 |
耦合度 | 观察者需实现特定接口,与主题接口耦合。 | 发送者和接收者无需知道对方存在(仅需信号签名匹配)。 |
线程安全性 | 需自行处理多线程同步。 | 支持跨线程通信(通过Qt::ConnectionType 指定连接方式)。 |
语法复杂度 | 需手动注册/注销观察者,代码量较多。 | 声明信号和槽后,通过connect 一键绑定,语法简洁。 |
动态性 | 运行时动态增减观察者较灵活。 | 支持动态连接/断开(connect /disconnect ),但依赖Qt框架。 |
类型安全 | 依赖接口约定,类型错误可能在运行时暴露。 | 编译时检查信号和槽的参数类型(需使用qRegisterMetaType 注册自定义类型)。 |
适用范围 | 通用设计模式,可用于任何C++环境。 | 依赖于Qt框架,非Qt项目无法使用。 |
关键差异点
-
框架依赖
- 观察者模式是语言中立的,适用于任何面向对象语言。
- 信号槽是Qt特有的机制,需继承
QObject
并使用moc
预处理。
-
连接方式
- 观察者模式:显式调用观察者的接口方法(如
update()
)。 - 信号槽:通过
connect
将信号与槽关联,事件触发时自动调用。
- 观察者模式:显式调用观察者的接口方法(如
-
性能开销
- 观察者模式:直接函数调用,效率高。
- 信号槽:涉及元对象系统查找,轻微性能损失(但通常可忽略)。
代码示例对比
观察者模式
class Observer
{
public:virtual void update(int data) = 0;
};class Subject
{std::vector<Observer*> observers;
public:void attach(Observer* obs) { observers.push_back(obs); }void notify(int data) {for (auto obs : observers) obs->update(data);}
};
Qt信号槽
class Sender : public QObject
{Q_OBJECT
signals:void dataChanged(int);
};class Receiver : public QObject
{Q_OBJECT
public slots:void handleData(int data) { /* ... */ }
};// 连接信号与槽
Sender sender;
Receiver receiver;
QObject::connect(&sender, &Sender::dataChanged, &receiver, &Receiver::handleData);
何时选择?
-
观察者模式:
- 非Qt项目或需要轻量级解耦。
- 需要精细控制观察者生命周期(如游戏引擎中的事件系统)。
-
Qt信号槽:
- Qt项目中优先使用,尤其涉及GUI或跨线程通信。
- 需要快速实现松耦合且减少样板代码。
总结
Qt信号槽可以视为观察者模式在Qt框架中的优化实现,它通过元对象系统提供了更高层次的抽象,牺牲少量性能换取开发效率和解耦程度。而观察者模式更灵活,适合无框架依赖的场景。
我在QT项目中目前还没有使用到观察者模式