Skip to content

Qt 设计模式:中介者模式

系列:Qt / VTK 设计模式 · Qt 10/11
参考:Qt 6 The Event SystemQApplication::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)一对多广播通知:主语触发变化,订阅者单向响应主语不依赖具体观察者,但知道观察者接口

最佳实践与踩坑避坑

  1. eventFilter 返回值的规范
    • 返回 true:表示事件已经被中介者消费/拦截,Qt 不会再将其分发给目标 QWidget
    • 返回 false:表示中介者仅做窥探或记录,事件继续向下正常传递。
  2. 避免中介者膨胀为“上帝类 (God Object)”
    • 当界面过于复杂时,不要把全软件几百个控件都交给一个巨大的 Mediator。应按功能模块划分为多个局部中介者(如 ToolbarControllerCanvasController)。

重点与注意

重点QApplication::notify() 是 Qt 整个框架中最高权重的中央中介者分发入口。
重点installEventFilter 允许在无需修改目标控件源码的前提下注入中介拦截逻辑。
注意:多控件互相连接时,必须警惕信号引发的递归循环,可在中介者中引入防重入标志位(m_isUpdating)。


小结

Qt 中介者模式将多对多的网状关系重构成星型依赖,极大降低了复杂 UI 系统与事件管线的耦合度。

延伸阅读

基于 VitePress 强力驱动 | 记录技术与生活