Qt 设计模式:中介者模式
系列:Qt / VTK 设计模式 · Qt 10/11
参考:Qt 6 The Event System、QApplication::notify
引子
在一个复杂的控制界面中,滑块(Slider)、微调框(SpinBox)、文本框(LineEdit)和画板(Canvas)如果两两互相连接 connect,哪怕只有 5 个控件,连接关系也会剧增到 网状依赖——改动任何一个控件的业务都会导致整个交互链条崩溃。
解决此问题的经典方案是中介者模式(Mediator Pattern):用一个中介对象来封装一系列的对象交互,使各对象不需要显式地相互引用,从而使其耦合松散。在 Qt 中,中介者模式体现在底层中央事件分发管线(QCoreApplication::notify / eventFilter)以及上层 UI 控制器(Controller)的集中解耦中。
核心痛点与 GoF 中介者结构
痛点场景:网状多对多耦合
cpp
// 坏方案:控件之间相互 connect 交叉绑死
connect(slider, &QSlider::valueChanged, spinBox, &QSpinBox::setValue);
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), slider, &QSlider::setValue);
connect(slider, &QSlider::valueChanged, canvas, &Canvas::setBrushSize);
connect(colorPicker, &ColorPicker::colorChanged, canvas, &Canvas::setBrushColor);
// 控件数量增多时产生复杂的网状交织- 网状交织死锁:相互发射信号触发循环更新,极易导致堆栈溢出崩溃。
- 扩展性极差:增加或移除一个控件,需要在其他多个控件中清理连接逻辑。
GoF 中介者结构与 Qt 对应映射
| GoF 中介者角色 | Qt 对应实现 | 职责说明 |
|---|---|---|
| 中介者接口 (Mediator) | QCoreApplication / QObject 控制器 | 定义组件间协作与通信的统一调度接口 |
| 具体中介者 (ConcreteMediator) | QApplication::notify / FormController | 拦截、处理或统筹协调各个组件之间的消息传递 |
| 同事组件 (Colleague) | QWidget, QSlider, QSpinBox | 仅与中介者交互,不知道其他同级组件的物理存在 |
底层原理:中央事件分发与事件过滤器链
Qt 的底层事件系统是天然的中介者架构:所有 QEvent 对象必须经由 QCoreApplication 中央中介者统筹分发。
完整代码示例
1. 应用层:UI 控制器作为中介者解耦控件网
cpp
#include <QApplication>
#include <QWidget>
#include <QSlider>
#include <QSpinBox>
#include <QVBoxLayout>
#include <QDebug>
// 中介者类:集中管控 Slider 与 SpinBox 的交互,防止网状 connect
class VolumeController : public QObject {
Q_OBJECT
public:
VolumeController(QSlider* slider, QSpinBox* spinBox, QObject* parent = nullptr)
: QObject(parent), m_slider(slider), m_spinBox(spinBox) {
// 控件只与中介者建立单向联系,不直接互相连接
connect(m_slider, &QSlider::valueChanged, this, &VolumeController::onSliderChanged);
connect(m_spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &VolumeController::onSpinBoxChanged);
}
private slots:
void onSliderChanged(int value) {
if (m_isUpdating) return; // 防递归保护
m_isUpdating = true;
m_spinBox->setValue(value);
qDebug() << "Mediator Synced Volume to:" << value;
m_isUpdating = false;
}
void onSpinBoxChanged(int value) {
if (m_isUpdating) return;
m_isUpdating = true;
m_slider->setValue(value);
qDebug() << "Mediator Synced Volume to:" << value;
m_isUpdating = false;
}
private:
QSlider* m_slider;
QSpinBox* m_spinBox;
bool m_isUpdating = false; // 保护标志位
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
QVBoxLayout* layout = new QVBoxLayout(&window);
QSlider* slider = new QSlider(Qt::Horizontal);
QSpinBox* spinBox = new QSpinBox();
slider->setRange(0, 100);
spinBox->setRange(0, 100);
layout->addWidget(slider);
layout->addWidget(spinBox);
// 实例化中介者解耦
VolumeController controller(slider, spinBox, &window);
window.show();
return app.exec();
}
#include "main.moc"2. 底层:通过 installEventFilter 中介拦截快捷键
cpp
// 全局键盘按键中介过滤器
class GlobalShortcutMediator : public QObject {
Q_OBJECT
protected:
bool eventFilter(QObject* watched, QEvent* event) override {
if (event->type() == QEvent::KeyPress) {
auto* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_F1) {
qDebug() << "Global F1 Pressed! Intercepted by Mediator for:" << watched;
return true; // 返回 true 表示事件被中介者消费,不再传给被观察对象
}
}
return QObject::eventFilter(watched, event);
}
};易混淆模式对比
| 设计模式 | 关注点 | 通信拓扑关系 |
|---|---|---|
| 中介者模式 (Mediator) | 多对多集中协调:通过中介者将复杂的网状依赖转化为星型依赖 | 组件仅与中介者双向通信,组件间无感知 |
| 观察者模式 (Observer) | 一对多广播通知:主语触发变化,订阅者单向响应 | 主语不依赖具体观察者,但知道观察者接口 |
最佳实践与踩坑避坑
eventFilter返回值的规范:- 返回
true:表示事件已经被中介者消费/拦截,Qt 不会再将其分发给目标QWidget。 - 返回
false:表示中介者仅做窥探或记录,事件继续向下正常传递。
- 返回
- 避免中介者膨胀为“上帝类 (God Object)”:
- 当界面过于复杂时,不要把全软件几百个控件都交给一个巨大的 Mediator。应按功能模块划分为多个局部中介者(如
ToolbarController、CanvasController)。
- 当界面过于复杂时,不要把全软件几百个控件都交给一个巨大的 Mediator。应按功能模块划分为多个局部中介者(如
重点与注意
重点:
QApplication::notify()是 Qt 整个框架中最高权重的中央中介者分发入口。
重点:installEventFilter允许在无需修改目标控件源码的前提下注入中介拦截逻辑。
注意:多控件互相连接时,必须警惕信号引发的递归循环,可在中介者中引入防重入标志位(m_isUpdating)。
小结
Qt 中介者模式将多对多的网状关系重构成星型依赖,极大降低了复杂 UI 系统与事件管线的耦合度。
延伸阅读