Qt中的桥接模式:从QPA到多通道日志系统的架构实践
2026/9/14 19:58:35 网站建设 项目流程

1. 先聊聊Qt里最常见的三种"桥"

在Qt开发中,"桥"这个字其实无处不在。你可能没意识到,自己每天敲的代码里早就用上了Bridge模式,只不过很少有人把它单独拎出来讲。我在实际项目里见过太多因为没理解"桥"而把代码写成一坨的情况——UI逻辑和业务逻辑纠缠在一起、第三方SDK换一个版本就要改半套代码、底层事件想通知界面却不知道该怎么传递。这些问题用一句话概括就是:抽象和实现没有分离,而Bridge模式(桥接模式)正是解决这个问题的标准答案。

Bridge模式的结构并不复杂:它把"抽象部分"和"实现部分"分开,让两边各自独立演化,中间通过一个桥梁接口连接。举一个最简单的场景:你的应用需要一个日志模块,开发时输出到控制台方便调试,上线后写入文件方便排查问题,哪个运维团队不需要这个?如果直接在业务类里写死qDebug()QFile,代码还能看吗?这时用Bridge模式,把"日志行为"抽象成一个接口,控制台版、文件版各自实现这个接口,业务层只管调用,后续再增加网络传输版、数据库版都无需改动业务层代码。

不过要提醒一点:很多初学者会把Bridge模式和策略模式、装饰器模式混淆。它们确实长得像,都是从接口出发做多态替换,但Bridge模式的本质是"一个类持有另一个类的引用,且两者都可以独立扩展"。策略模式下,持有策略的类通常是稳定的;而Bridge模式下,抽象部分和实现部分往往是两个维度,比如"日志"是抽象维度,"控制台/文件/网络"是实现维度,"UI窗口"是抽象维度,"Windows/Wayland/macOS平台适配"是实现维度——这就是Qt本身在做的事。

具体到Qt生态,我总结了三种最常见的"桥"场景,是你在C++项目里一定会遇到的。

1.1 最经典的桥:QPA——Qt自己的平台抽象层

很多人在Windows上用Qt写界面,在Linux上交叉编译跑嵌入式板子,代码不动却能在两套平台上显示完全一致。背后靠的就是QPA(Qt Platform Abstraction)。你可以把QPA理解成Qt自己实现的一个巨型Bridge模式:上面是一套统一的窗口系统API(QWindow、QPlatformWindow、QPlatformIntegration),下面是各个平台的实现插件——Windows平台一个dll,Linux/X11一个so,Wayland一个so,Android和iOS又各有各的适配。

这套架构最大的好处是:你写的业务代码不需要关心底层到底是X11还是Wayland。换平台?Qt帮你换实现。而且因为抽象和实现是分开的,厂商可以自己在不修改Qt源码的前提下写新的平台插件,比如树莓派上跑的那套EGLFS就是这么来的。Qt官方代码库中qplatformintegration.h这个头文件就是那个"桥"的接口定义。

1.2 最常见的工作场景:封装第三方SDK

我在实际项目里用Bridge模式最多的地方,就是封装第三方SDK。比如项目里要接一个扫码库,今天用A厂商的,明天客户说换成B厂商的驱动,后天又要在模拟器上跑一套Mock实现。如果业务代码直接调用SDK的类,每次切换都是噩梦。

用Bridge模式封装后,业务层面对的是自己定义的ScannerBridge接口,A厂商、B厂商、Mock分别实现这个接口。切换时只需要改一个工厂函数里返回的具体对象,其他所有业务代码一行都不用动。这个思路在接摄像头、接打印SDK、接地磁传感器、接蓝牙模块的时候同样适用。

1.3 最容易忽视的桥:UI层与业务层的隔离

很多Qt新手写出来的程序,数据库查询逻辑、网络请求逻辑、数据计算逻辑全部堆在MainWindow的成员函数里。看起来能跑,但一旦界面复杂起来,窗口类就变成几千行的巨兽,改个UI都要担心会不会碰坏业务代码。

正确做法是让MainWindow只负责界面事件的分发和展示,把真正干活的逻辑全部放到独立的业务类中。这里的"桥"就是Qt最强的信号槽机制——界面按钮被点击时发出信号,业务对象接住信号开始干活,干完活再发信号通知界面更新。界面不直接持有业务对象的实现细节,业务代码也不需要知道界面长什么样。这也是Bridge模式在Qt开发中最天然的落地方式。


2. 一个完整案例:可切换后端的多通道日志系统

讲理论很容易,但要把一个模式吃透,必须自己动手搭一遍。我先用一个贯穿全篇的项目案例来讲:一个基于Bridge模式的多通道日志系统。选这个案例有三个原因:第一,日志系统几乎是每个Qt项目都需要的组件,读者能直接迁移到自己的项目里;第二,它的"抽象维度"(日志级别、日志格式)和"实现维度"(输出到控制台、文件、网络)分得非常清晰,天然适合Bridge;第三,它能自然地引出信号槽、线程、生命周期这些Qt开发里绕不开的问题。

2.1 需求分析与抽象设计

假设我接到一个需求:做一个桌面数据采集软件,需要一套日志库。

  • 开发阶段:日志要实时打到控制台,方便看调试信息;
  • 正式运行:日志要写进按天滚动的文件,方便出事之后回溯;
  • 未来可能的扩展:日志通过网络发到监控中心,或者同时在UI窗口里开一个"实时日志"面板展示。

如果不用Bridge模式,我会怎么做?大概率在每个类里直接写qDebug(),或者调用一个全局日志函数,函数内部用条件判断切换输出方式。这个方案在只有一个输出通道时没问题,但一旦要"同时输出到文件和网络",代码就会变成一堆if分支,而且每加一种通道就要改一遍调用链。

用Bridge模式设计,从第一天就把结构立好:

  • Logger(抽象):

    • 定义日志的公开API:debug()info()warn()error()
    • 负责统一处理日志级别过滤、时间戳格式化等与"输出方式无关"的逻辑
    • 持有实现接口的指针
  • LogBackend(实现接口):

    • 定义底层输出动作:write(const QString &formattedLine)
    • 只关心"把这条字符串送到哪里",不关心这个字符串怎么拼出来的
  • ConsoleBackend/FileBackend/NetworkBackend(具体实现):

    • 分别把字符串输出到控制台、文件、UDP套接字

这就把一个日志系统的两个变化维度拆开了:日志内容怎么格式化,是Logger的事;格式化之后往哪儿送,是Backend的事。两边都可以独立演化,互不干扰。

2.2 核心代码:接口与抽象类的实现

先定义实现接口。这是Bridge模式里的"桥"本体,名字叫LogBackend

// LogBackend.h class LogBackend { public: virtual ~LogBackend() = default; virtual void write(const QString &formattedLine) = 0; virtual void flush() {} }; using LogBackendPtr = std::shared_ptr<LogBackend>;

然后定义抽象层Logger。这里的重点是:Logger不关心LogBackend的具体类型,只通过LogBackend::write()接口发数据。同时Logger允许持有多个后端(比如同时写文件和发网络),这体现了Bridge模式里抽象部分可以灵活组合实现部分。

// Logger.h #include <QString> #include <QVector> #include <memory> #include <QDateTime> class LogBackend; class Logger { public: // 设置日志级别:低于此级别的日志直接丢弃 enum Level { Debug = 0, Info = 1, Warn = 2, Error = 3 }; explicit Logger(Level minLevel = Debug); ~Logger(); // 挂接/移除一个后端 void attachBackend(std::shared_ptr<LogBackend> backend); void detachAll(); void debug(const QString &msg); void info(const QString &msg); void warn(const QString &msg); void error(const QString &msg); private: void log(Level level, const QString &msg); struct Private; Private *d; };

有人会问:既然有std::shared_ptr,为什么还用Private *d这种Pimpl手法?这里其实是我故意埋的彩蛋——Bridge模式和Pimpl(d指针)在实践中的结合度极高,后面我会专门讲。先来看实现:

// Logger.cpp #include "Logger.h" #include "LogBackend.h" struct Logger::Private { QVector<LogBackendPtr> backends; Level minLevel; }; Logger::Logger(Level minLevel) : d(new Private) { d->minLevel = minLevel; } Logger::~Logger() { delete d; } void Logger::attachBackend(LogBackendPtr backend) { if (backend) { d->backends.append(std::move(backend)); } } void Logger::detachAll() { d->backends.clear(); } void Logger::debug(const QString &msg) { log(Debug, msg); } void Logger::info(const QString &msg) { log(Info, msg); } void Logger::warn(const QString &msg) { log(Warn, msg); } void Logger::error(const QString &msg) { log(Error, msg); } void Logger::log(Level level, const QString &msg) { if (level < d->minLevel) return; static const char *levelNames[] = { "DEBUG", "INFO", "WARN", "ERROR" }; const QString timeStamp = QDateTime::currentDateTime() .toString("yyyy-MM-dd hh:mm:ss.zzz"); // 格式化统一在这里做,后端只负责“运输” const QString line = QString("[%1] [%2] %3") .arg(timeStamp) .arg(levelNames[static_cast<int>(level)]) .arg(msg); for (auto &backend : d->backends) { if (backend) backend->write(line); } }

我可以负责任地说,Logger这个类的设计在真实项目里是完全够用的。它把"日志级别过滤""时间格式化""多个后端的遍历"这些公共能力集中在抽象层,这种做法本身就是Bridge模式的优势——公共逻辑不重复,具体差异交给实现层

2.3 两个具体实现:控制台后端与文件后端

ConsoleBackend很简单,用QTextStream写标准输出:

// ConsoleBackend.h #include <QObject> #include "LogBackend.h" class ConsoleBackend : public QObject, public LogBackend { Q_OBJECT public: explicit ConsoleBackend(QObject *parent = nullptr); void write(const QString &formattedLine) override; void flush() override; signals: // 这个信号的作用后面会展开讲 void lineWritten(const QString &line); }; // ConsoleBackend.cpp #include "ConsoleBackend.h" #include <QTextStream> ConsoleBackend::ConsoleBackend(QObject *parent) : QObject(parent) { } void ConsoleBackend::write(const QString &formattedLine) { QTextStream(stdout) << formattedLine << Qt::endl; emit lineWritten(formattedLine); } void ConsoleBackend::flush() { QTextStream(stdout).flush(); }

FileBackend就要讲些细节了。写文件不是拼个QFile就行,还有一个经常被忽视的问题:多线程环境下频繁写入会卡住业务线程。所以我用了一个Qt内置的巧妙方案——把写文件的动作放在QFile自己的事件循环线程里执行,主线程调用write时只投递一个事件,立刻返回。具体实现可以借助QMetaObject::invokeMethodQt::QueuedConnection

// FileBackend.h #include <QObject> #include <QFile> #include "LogBackend.h" class FileBackend : public QObject, public LogBackend { Q_OBJECT public: explicit FileBackend(const QString &filePath, QObject *parent = nullptr); ~FileBackend() override; void write(const QString &formattedLine) override; void flush() override; private slots: void appendLine(const QString &line); void flushFile(); private: QFile m_file; QTextStream m_stream; };

实现文件里有个关键点:write()是被业务线程直接调用的,我不能在这里操作QFile,而是通过信号槽把数据投递到FileBackend所在线程的事件循环里。这也是跨线程操作QObject子对象的标准姿势。

// FileBackend.cpp #include "FileBackend.h" #include <QDir> FileBackend::FileBackend(const QString &filePath, QObject *parent) : QObject(parent) , m_file(filePath) { QDir().mkpath(QFileInfo(filePath).absolutePath()); if (m_file.open(QIODevice::WriteOnly | QIODevice::Append)) { m_stream.setDevice(&m_file); } } FileBackend::~FileBackend() { flushFile(); m_file.close(); } void FileBackend::write(const QString &formattedLine) { // 跨线程投递,不阻塞调用线程 QMetaObject::invokeMethod(this, [this, formattedLine]() { appendLine(formattedLine); }, Qt::QueuedConnection); } void FileBackend::appendLine(const QString &line) { if (m_file.isOpen()) { m_stream << line << Qt::endl; m_stream.flush(); } } void FileBackend::flush() { QMetaObject::invokeMethod(this, &FileBackend::flushFile, Qt::BlockingQueuedConnection); } void FileBackend::flushFile() { if (m_file.isOpen()) m_stream.flush(); }

写完这两个后端,组装起来就是一套可用的日志库了。在main()里初始化:

#include <QCoreApplication> #include "Logger.h" #include "ConsoleBackend.h" #include "FileBackend.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); auto *fileBackend = new FileBackend("logs/app.log"); Logger logger(Logger::Debug); logger.attachBackend(std::make_shared<ConsoleBackend>()); logger.attachBackend(std::shared_ptr<LogBackend>(fileBackend)); logger.error("数据库连接失败,正在重试..."); logger.info("用户登录成功: admin"); return app.exec(); }

这就是一次完整的Bridge模式落地。你注意到没有:Logger不知道ConsoleBackendFileBackend的存在,它们只是两个LogBackend。将来要加一个NetworkBackend,只需要继承LogBackend实现write(),然后在main()里多挂一个attachBackend,业务调用处的代码一行不改。


3. 当Bridge遇到信号槽:让桥"活"起来

上一节的基础案例是"上层调用下层"的单向桥。但在实际Qt开发里,桥往往是双向的——不仅上层要发命令给下层,下层也要往回上报事件。这一节我重点讲如何用信号槽机制给Bridge模式装上"反向通道",这也是纯C++的Bridge模式在Qt体系里独有的进化点。

3.1 事件上抛的三种做法对比

假设我的日志系统需要把日志内容实时显示在UI窗口的QPlainTextEdit里。怎么把Backend产生的事件抛给控件?我总结过三种做法:

做法一:直接在Backend里写UI代码最粗暴,让ConsoleBackend持有QPlainTextEdit*,往里面塞文本。后果前面已经批评过了——后端和UI耦合死,换一个UI框架就要重写后端。

做法二:在Logger层转发信号Logger规定一个newLog(const QString&)信号,后端每次写入后,通过上层转发给所有关心者。这样后端不需要知道UI的存在,但缺点是每加一种事件类型就要改上层接口,桥会越来越重。

做法三:后端自带信号,谁关心谁自己连接也就是我在ConsoleBackend里定义的lineWritten(const QString&)信号。UI层主动connect这个信号,业务层不知道UI的存在,后端也不需要感知UI。桥的两端完全解耦,事件流想加几条加几条。

第三种做法最符合Qt的设计哲学。信号槽本身就是一种消息桥,它和Bridge模式重叠之后,等于给两个独立演化的维度之间加了一条既松耦合又强感知的通道。

3.2 用信号槽接通UI面板

在上一节案例基础上扩展:在UI里加一个"实时日志"面板。

// LogWidget.h #include <QWidget> class QPlainTextEdit; class LogBackend; class LogWidget : public QWidget { Q_OBJECT public: explicit LogWidget(QWidget *parent = nullptr); // 把这个控件挂到某个后端上 void attachToBackend(QObject *backend); private slots: void onLogLine(const QString &line); private: QPlainTextEdit *m_editor; };

attachToBackend的实现很有意思,它接受的是一个QObject*而不是LogBackend*,然后在运行时查找是否有lineWritten信号。这比在头文件里硬编码一个ConsoleBackend*参数要灵活得多,因为以后不管哪个后端(文件也好、网络也好)只要定义了lineWritten信号,都能接到这个面板上:

// LogWidget.cpp #include "LogWidget.h" #include <QPlainTextEdit> #include <QVBoxLayout> #include <QMetaObject> LogWidget::LogWidget(QWidget *parent) : QWidget(parent) , m_editor(new QPlainTextEdit(this)) { auto *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_editor); m_editor->setReadOnly(true); m_editor->setMaximumBlockCount(2000); // 防止内存无限增长 } void LogWidget::attachToBackend(QObject *backend) { if (!backend) return; // 动态连接:只要backend有lineWritten(QString)信号就能连上 QObject::connect(backend, SIGNAL(lineWritten(QString)), this, SLOT(onLogLine(QString))); } void LogWidget::onLogLine(const QString &line) { m_editor->appendPlainText(line); }

注意connect用的还是旧的SIGNAL/SLOT宏字符串形式。为什么不用新式&Class::signal写法?因为这里backend是一个运行时对象,编译期只知道它是QObject*,根本拿不到ConsoleBackend::lineWritten的成员函数指针。用字符串方式做运行时解析,恰恰是Qt元对象系统的特长,也是这一类"泛型桥接"场景下的正确选择。

3.3 反向桥的核心:事件优先级与队列

信号槽连接默认有AutoConnectionDirectConnectionQueuedConnection三种模式。在Bridge模式里,底层实体经常运行在非UI线程,信号槽跨线程时的队列处理就非常重要。

比如我的FileBackend如果跑在独立线程中,它emit lineWritten(...)时,UI线程的onLogLine会通过事件队列收到这个信号,两个线程之间不会出现同时访问QPlainTextEdit的竞态问题——因为Qt的QueuedConnection本质上就是往接收者线程的事件循环里投递了一个事件。

我踩过一个坑:某个硬件采集模块在子线程里发数据信号,我在主线程UI里直接连接,以为Qt会自动处理跨线程,结果程序频繁崩溃。后来排查发现硬件模块的父对象是主线程对象,但我在子线程里直接调用了它的信号发射方法,类型判断误判成了DirectConnection,最后绕开了事件队列、在子线程里直接操作了UI控件。解决办法是确保发射信号的对象本身归属在发射线程,或者手动指定Qt::QueuedConnection。这个细节对任何用Bridge模式跨线程传递事件的人都成立。


4. 和Pimpl一脉相承:Qt源码里的双指针智慧

我在第2节的Logger里故意用了struct Private; Private *d;这种写法。很多读者会想:这不是Pimpl惯用法(d-pointer)吗?和Bridge有什么血缘关系?答案是:Qt源码里的d_ptr/q_ptr同时实现了Pimpl和Bridge两种模式的双重好处,理解这一点,你才算真正看懂Qt的类设计。

4.1 Qt的d_ptr/q_ptr是什么

Qt几乎每个公开类都有Q_D定义一个Q_DECLARE_PRIVATE,比如QWidget里有Q_D(QWidget)获得QWidgetPrivate *d,而这个QWidgetPrivate就是QWidget对外的"实现部分"。外界只能操作QWidget本身,内部真正的数据属于QWidgetPrivate

这为什么是Bridge?因为QWidget负责对外API(抽象部分),QWidgetPrivate负责细节和平台适配(实现部分)。两者通过d_ptr这座桥连接,而且Qt利用继承机制让子类(如QPushButtonQLineEdit)的d_ptr类型自动变成对应子类私有类,实现了抽象维度和实现维度的同时扩展。这正是Bridge模式中"两个维度独立扩展"的教科书级体现。

4.2 自己动手写一个带d_ptr的桥

在业务代码里我不建议照搬Qt那套宏,因为宏会降低可读性。但思路可以吸收。我在真实项目里是这样写一个"数据采集"桥的:

// Acquirer.h #include <memory> class AcquirerBackend; class Acquirer { public: enum Status { Idle, Running, Error }; explicit Acquirer(std::shared_ptr<AcquirerBackend> backend); ~Acquirer(); void start(); void stop(); Status status() const; private: struct Private; Private *d; };

Acquirer是抽象部分,AcquirerBackend是实现接口。Private这个结构体里挂了backendstatus等状态数据。好处很明显:

  • 所有成员变量都被藏进.cpp文件,头文件干净到只有类声明,编译依赖大幅降低;
  • 实例化Acquirer和删除Acquirer时,完成Private的构建与析构,二进制兼容性更好;
  • 实现部分(AcquirerBackend)可以像Logger那样自由替换。

4.3 虚函数与私有的边界

有个高频面试题顺带讲一下:为什么Qt要费劲搞d_ptr,而不直接在子类里加成员变量?原因是在C++里,在基类对象末尾添加成员变量会改变子类对象的布局,破坏二进制兼容性。用d_ptr把成员数据放堆上,父类只需保存一个指针,子类无论怎么加数据,sizeof不变,ABI自然稳定。

但这里有一个边界要特别小心:d_ptr和虚函数是两种不同的扩展机制,不要混用。虚函数解决的是"接口分派"问题,d_ptr解决的是"数据隐藏和ABI稳定"问题。Bridge模式的抽象部分可以用虚函数去多态,但数据存储更适合放Private。在我写的Logger里,attachBackendlog这些行为走的是接口虚函数,而后端列表、最低日志级别这些数据则放进了Logger::Private,分工很清晰。


5. 实操避坑:这些坑我替你先踩了一遍

理论讲得再漂亮,代码写得再工整,项目一跑总会出幺蛾子。下面几个坑是我在不同项目里真实踩过的,都和Bridge模式在Qt中的落地产物直接相关,写出来给后来者提个醒。

5.1 别把Factory当成Bridge

很多初学者看完模式理论之后动手写代码,写出来的东西其实是Factory(工厂)而不是Bridge。区别很简单:Factory的意图是"创建对象时隐藏具体类型",Bridge的意图是"抽象和实现分离且各自演化"。如果你的接口下只有一个实现类,或者实现的维度不会扩展,那老老实实写一个简单工厂就够了,套Bridge反而增加无谓的层级。

我在项目里见过的反面例子:一个负责发送HTTP请求的类,功能就是post(),所有平台后端的差异仅仅是一个URL前缀,结果硬搞出HttpBridgeHttpBackendPlatformHttpBackend三层接口。代码量翻倍,收益几乎为零。记住:模式是为解决问题服务的,不是为展示技巧服务的。

5.2 反向桥中的重入与死锁

用信号槽做反向桥的时候,有一个隐患容易被忽略:信号在槽函数里又被触发回桥。举个真实例子:我的FileBackend每次写完日志会emit lineWritten(...),UI面板收到信号后如果又调用logger.info("UI更新完成"),就形成了"日志写入 -> 信号 -> UI处理 -> 再次写日志 -> 信号 -> ..."的循环。日志系统还好,如果桥上的信号是"数据到达"而槽里又要"请求更数据处理",栈溢出只是时间问题。

解决办法有三种,按实际需求取舍:

  1. 信号里不带触发源数据,只传必要载荷,让下游无法"原路打回";
  2. UI层的槽函数里做标志位保护,比如bool m_updating,重入时直接return
  3. Qt::QueuedConnection把同步重入变成异步排队,栈深度自然缓解。

5.3 析构顺序:先拆桥、再拆两岸

这是Bridge模式在C++里最经典的坑。考虑这个场景:Logger持有std::shared_ptr<LogBackend>,UI的LogWidget持有后端指针并连接了信号槽。假设一个FileBackend对象被widget这个界面显示着,同时又被logger引用着,当窗口关闭时,你期望的顺序是:先让Logger别再往里写入,再释放后端,再让UI控件销毁。

但如果你用的是裸指针分配后端,然后在Loggerdelete它,而在LogWidget里还在连接它的信号,析构顺序一倒,轻则崩溃,重则内存越界。我推荐的方案是:

  • 后端对象统一用std::shared_ptr<LogBackend>管理,谁都需要就谁持有shared_ptr;
  • LogWidget的析构或关闭事件里调用disconnect(),确保它不再接收任何信号;
  • 关闭窗口时先调用logger.detachAll(),再销毁窗口。

还有一个细节:如果一个类同时继承QObjectLogBackenddelete该对象时,QObject子系统的信号连接会由QObject析构自动清理,但这依赖于对象归属的父对象正确。如果你把这样一个后端new出来挂到某个父对象下,又把它放进shared_ptr,就会出现两个所有权系统争夺同一个对象——这是我实际调试过的崩溃源头之一。解决方式是二选一:要么只用Qt父对象体系管理生命周期,要么只用shared_ptr管理,不要在两者之间来回横跳。

5.4 跨线程时后端收到的事件可能乱序

如果你的FileBackend跑在独立线程,而多个业务线程同时向Logger写入日志,信号槽跨线程投递的事件在进入事件循环时可能因为系统调度而轻微乱序。对日志系统来说,"同一毫秒的两条日志先后顺序颠倒"往往是可以容忍的;但如果你用Bridge模式封装硬件指令,乱序是绝对不行的。

我的处理方法是:在抽象层Logger::log()里加一把QMutex锁,保证格式化之后write的动作是有序的;不是锁具体的后端,因为后端可能跨线程,锁住抽象层才能保证所有线程的日志进入后端之前就排好队。当然这会对高频日志引入一定开销,但把锁粒度控制在"格式化+放入队列"这一步,实测比直接锁IO划算得多。


最后再分享一个我个人的小技巧:初次接触Bridge模式时,先别看UML图,先用"维度表"思考。在一张纸上画出这个类体系里可能变化的两个方向——比如"日志输出到哪里"是一个方向、"日志怎么格式化"是另一个方向——如果两个方向未来都有独立扩展的可能性,那Bridge就是正确答案;如果只有一个方向会变,用接口多态就够了。这个判断方法我在设计通信协议解析层、音视频渲染层、硬件驱动封装层时反复用过,几乎没有失手过。你看,Bridge模式在Qt里其实没那么多玄学,它就是你项目里那堆"上层换场景、下层换实现"问题的解药。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询