Skip to content

Qt 设计模式:策略模式

系列:Qt / VTK 设计模式 · Qt 04/11
参考:Qt 6 QIODeviceQAbstractItemModelQStyle


引子

同段业务代码,一会儿要从磁盘文件读取,一会儿要从 TCP 网络 Socket 读取,一会儿又要从内存 QBuffer 中读取——如果直接写满 if-else 分支,代码将变得极其臃肿。

解决此问题的经典方案是策略模式(Strategy Pattern)定义一族算法/行为,将它们封装起来,并使它们可以相互替换。在 Qt 中,策略模式普遍体现为“抽象基类定义统一接口 + 运行时动态注入实现”,做到了业务调用方与底层算法的具体实现解耦。


核心痛点与 GoF 策略结构

痛点场景:大量条件分支的硬编码

cpp
// 坏方案:直接根据类型分支调用具体读写函数
void processData(int type, const QByteArray& input) {
  if (type == 1) {
    // 处理文件 IO 细节
  } else if (type == 2) {
    // 处理 Socket 网络 IO 细节
  } else if (type == 3) {
    // 处理内存 Buffer 细节
  }
}
  • 违反开闭原则(OCP):每当新增一种数据源或计算算法,都必须修改核心控制逻辑。
  • 高耦合难测试:无法通过 Mock 对象单独测试核心业务逻辑。

GoF 策略模式结构映射

GoF 策略角色Qt 对应实现职责说明
抽象策略 (Strategy)QIODevice / QAbstractItemModel / QStyle定义统一的抽象操作接口(如 readData(), data(), drawControl()
具体策略 (ConcreteStrategy)QFile, QTcpSocket, QBuffer / QFusionStyle实现具体的算法、IO 读写逻辑或界面绘制逻辑
上下文 (Context)持有 QIODevice*QStyle* 的应用层代码维护策略对象的引用,通过抽象接口调用策略功能

底层原理与策略切换流向

策略模式的核心是依赖倒置原则(DIP):高层业务逻辑只依赖抽象接口,不依赖具体的子类实现。


Qt 策略体系经典落点

1. QIODevice 体系(IO 策略)

QIODevice 抽象基类定义了 read(), write(), seek() 接口:

  • QFile:磁盘文件读写策略。
  • QTcpSocket / QUdpSocket:网络套接字读写策略。
  • QBuffer:内存字节数组读写策略。

调用方(如 QTextStreamQDataStream)只需接收 QIODevice* 指针,即可无缝切换策略。

2. QStyle 体系(GUI 控件绘制策略)

Qt Widgets 界面控件的绘制行为完全被抽象到了 QStyle 策略类中:

  • 通过 QApplication::setStyle(QStyleFactory::create("Fusion")) 可以在运行时动态更换整个应用的渲染风格,而无需动任何 QPushButtonQSlider 控件源码。

3. QAbstractItemModel(数据获取策略)

QListViewQTableView 视图组件只依赖 QAbstractItemModel* 接口获取数据:

  • 运行时可以在 QSqlTableModel(数据库策略)、QStandardItemModel(内存节点策略)之间无缝切换。

完整代码示例

1. 基于 QIODevice 的可替换数据处理

cpp
#include <QCoreApplication>
#include <QFile>
#include <QBuffer>
#include <QDebug>

// 业务上下文函数:只依赖抽象策略 QIODevice*
void parseStreamData(QIODevice* device) {
  if (!device->isOpen() && !device->open(QIODevice::ReadOnly)) {
    qWarning() << "Failed to open device";
    return;
  }
  
  while (!device->atEnd()) {
    QByteArray line = device->readLine();
    qDebug() << "Parsed Line:" << line.trimmed();
  }
}

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

  // 策略 1:内存数据策略
  QBuffer memoryBuffer;
  memoryBuffer.setData("Line1: Memory\nLine2: Buffer\n");
  parseStreamData(&memoryBuffer);

  // 策略 2:文件数据策略(完全无缝替换)
  QFile file("test.txt");
  if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
    file.write("Line1: File\nLine2: Disk\n");
    file.close();
  }
  parseStreamData(&file);

  return 0;
}

2. Modern C++ 函数对象 / Lambda 策略

在 C++11 后,对于轻量级算法策略,通常不需要创建复杂的类继承体系,直接使用 std::function 注入策略即可:

cpp
#include <QList>
#include <algorithm>
#include <functional>
#include <QDebug>

using SortStrategy = std::function<bool(int, int)>;

void customSort(QList<int>& data, SortStrategy strategy) {
  std::sort(data.begin(), data.end(), strategy);
}

// 使用策略
QList<int> numbers = { 5, 2, 8, 1, 9 };
// 升序策略
customSort(numbers, [](int a, int b) { return a < b; });
// 降序策略
customSort(numbers, [](int a, int b) { return a > b; });

易混淆模式对比

设计模式核心目的结构区别
策略模式 (Strategy)算法互换:在运行期由客户端指定具体的实现算法策略类平级,通常没有主从层次关系
状态模式 (State)状态驱动行为:由对象内部状态的改变自动切换行为状态类内部保存转换逻辑,客户端无需感知
模板方法模式 (Template Method)固定骨架:由父类定义步骤顺序,子类填空基于继承机制(编译期多态),策略基于组合(运行期多态)

最佳实践与踩坑避坑

  1. 优先依赖抽象接口:业务代码中应传递 QIODevice*QAbstractItemModel*,避免直接绑定 QFile*QStandardItemModel*
  2. 策略对象的生命周期控制:确保 Context 在调用策略时,策略对象未被提前销毁。如果策略由外部传入,应明确所有权(如通过 std::unique_ptrQObject 父子树管理)。
  3. 单元测试中的 Mock 优势:测试需要读取网络/文件的复杂业务逻辑时,直接注入 QBuffer 策略,无需搭建真实的服务器或创建物理磁盘文件。

重点与注意

重点:策略模式的核心是 “面向接口编程、运行期自由替换”;Qt 中的 QIODeviceQAbstractItemModel 是策略模式的典范。
重点:在 Modern C++ 中,std::function 与 Lambda 表达式是轻量策略调度的首选。
注意:策略模式适合算法独立、平级可替换的场景;如果行为随内部状态流转而自动变化,应选 状态模式


小结

Qt 策略模式打破了繁重条件分支的枷锁,使数据读取、格式渲染与排序算法能够弹性扩展。

延伸阅读

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