Skip to content

Qt 设计模式:模板方法模式

系列:Qt / VTK 设计模式 · Qt 05/11
参考:Qt 6 QThreadThe Event System


引子

写一个自定义 QThread 线程类时,你只需要在子类中重写 run() 方法;至于底层的操作系统线程创建、优先级设置、信号发射以及清理工作,全由基类 QThread 事先编排妥当——你只需要填入“可变的那一步”。

这就是模板方法模式(Template Method Pattern)定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构,即可重定义该算法的某些特定步骤。在 Qt 中,这一模式被广泛应用于线程入口、事件分发管线以及绘制流程中。


核心痛点与 GoF 结构

痛点场景:算法骨架重复与漏步骤

cpp
// 坏方案:每个线程实现类都重复编写线程初始化与清理骨架
void workerThreadA() {
  setupOSThread(); // 重复的底管代码
  doTaskA();       // 业务代码
  cleanupThread(); // 容易遗漏的清理代码
}
  • 代码大量冗余:相同的流程框架被散落在各个派生类中。
  • 一致性难保障:容易遗漏关键的初始化或清理步骤(产生资源泄漏)。

GoF 模板方法结构映射

GoF 抽象角色Qt 对应实现职责说明
抽象类 (AbstractClass)QThread / QWidget / QAbstractItemModel定义算法骨架方法 (start(), event()) 并声明虚钩子函数
模板方法 (TemplateMethod)QThread::start() / QWidget::event()编排固定的算法流程与调用顺序
原语操作 (PrimitiveOperation)QThread::run() / QWidget::paintEvent()由子类重写的高精细度定制步骤(钩子函数)

底层原理:好莱坞原则 (Don't call us, we'll call you)

模板方法模式的核心思想是控制反转——好莱坞原则(由基类掌控流程并反向调用子类)

对于事件分发,QWidget::event() 同样充当了模板方法骨架,根据事件类型分发给相应的钩子:


完整代码示例

1. QThread::run() 自定义线程执行体

cpp
#include <QCoreApplication>
#include <QThread>
#include <QDebug>

// 子类只需重写可变的 run() 方法
class DataProcessingThread : public QThread {
  Q_OBJECT

public:
  explicit DataProcessingThread(QString taskName) : m_taskName(std::move(taskName)) {}

protected:
  // 模板方法中的原语操作 (Primitive Operation)
  void run() override {
    qDebug() << "Thread Start Processing Task:" << m_taskName << "on Thread ID:" << QThread::currentThreadId();
    
    for (int i = 0; i < 3; ++i) {
      QThread::msleep(100);
      qDebug() << m_taskName << "Progress:" << (i + 1) * 33 << "%";
    }

    qDebug() << "Task Finished:" << m_taskName;
  }

private:
  QString m_taskName;
};

int main(int argc, char *argv[]) {
  QCoreApplication app(argc, argv);

  DataProcessingThread thread("Point Cloud Calculation");

  // 关键动作:调用基类的模板方法 start(),而非直接调用 run()
  thread.start();

  // 等待线程执行完成
  thread.wait();
  return 0;
}
#include "main.moc"

易混淆模式对比

设计模式扩展机制核心区别
模板方法模式 (Template Method)继承机制(编译期确定骨架)基类写死算法逻辑框架,子类重写局部虚函数步骤
策略模式 (Strategy)组合机制(运行期注入对象)客户端通过持有抽象接口指针,在运行时整体替换算法

最佳实践与踩坑避坑

  1. 绝对不要在业务中重写 QThread::start()
    • start() 是固定流程的模板方法(负责初始化线程环境)。如果覆盖重写了 start(),原生线程将无法正常启动。
  2. 现代 Qt 线程使用建议
    • 尽管 QThread::run() 是模板方法的经典体现,但在现代 Qt 开发中,处理轻量任务更推荐使用 QObject::moveToThread()QtConcurrent / QThreadPool
  3. paintEvent 中的父类调用
    • 在重写 QWidget::paintEvent() 编写自定义控件时,如果需要保留父控件的默认背景或阴影绘制,务必在最开头显式调用 QWidget::paintEvent(e)

重点与注意

重点:模板方法 = 基类固定算法骨架,子类填充特定步骤(通过重写虚函数)。
重点QThread::start() 是模板方法,QThread::run() 是供子类实现的钩子。
注意:模板方法基于类继承;策略模式基于接口组合。多用组合少用继承,但在框架底层设计中模板方法极其实用。


小结

Qt 模板方法模式隐藏了繁琐的系统底层初始化逻辑,让开发者只需专注实现核心业务切面。

延伸阅读

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