不能在ui类中直接new业务对象,因会导致ui强依赖具体实现、无法mock测试、更换实现需修改ui源码、跨线程易崩溃;应通过抽象接口指针(如icalculator*)和外部注入实现解耦。

直接用组合 + 接口指针,别让 QMainWindow 或 QWidget 子类直接调用业务函数。
为什么不能在UI类里直接 new 业务对象
常见错误是把 CalculatorEngine 或 DataProcessor 的实例直接 new 在 MainWindow 构造函数里,然后所有槽函数都通过 m_engine->doSomething() 调用。这看似分离,实则耦合严重:UI 类强依赖具体业务类类型,单元测试时无法 mock;换数据库实现就得改 UI 源码;更别说跨线程调用时容易崩溃。
真正要做的,是让 UI 只知道抽象接口,不关心谁实现它:
- 定义纯虚接口类(如
ICalculator),只暴露evaluate()、clear()等契约方法 - UI 类持有一个
ICalculator*成员,不是具体子类指针 - 构造时由外部注入(比如工厂或主函数 new 出具体实现再传入),UI 不负责生命周期
- 避免在 UI 类中
#include "CalculatorEngine.h",只 include 接口头文件
Qt 信号槽怎么不破坏分离原则
信号槽本身不破坏分离,但用法容易翻车。典型反模式:onButtonClicked() 里直接调用 m_engine->saveToDb() —— 这又把业务细节塞回 UI 层了。
正确做法是让 UI 只发“意图信号”,不发“执行指令”:
- UI 类 emit
requestSave()、inputChanged(const QString&),不带业务参数 - 另起一个协调类(比如
Controller)连接这些信号到具体业务对象的 slot - 或者用 Qt 的
QMetaObject::invokeMethod()做异步解耦,但仅限跨线程场景 - 绝对不要在 UI 类里 connect 到自己成员的 slot(比如
connect(this, &MainWindow::xxx, this, &MainWindow::handleXxx)),这等于把逻辑写死在 UI 里
QML + C++ 场景下怎么保持干净
QML 天然适合声明式 UI,但很多人把它当胶水层乱用:在 QML 里写 backend.calculate(...) 或直接操作 QSettings。这会让 UI 文件膨胀且不可测。
关键约束有三条:
- C++ 后端必须继承
QObject,并用Q_PROPERTY和Q_INVOKABLE暴露能力,而不是让 QML 直接访问私有成员 - QML 中只绑定属性、响应信号,所有计算/IO 都推给 C++ 后端完成;QML 里禁止出现
if (x > 10) { ... }这类业务判断 - 如果后端需要更新 UI,用 signal + property notify 机制,而不是从 C++ 主动调用
rootObject->setProperty(...) - 避免在
main.cpp里用rootObject->findChild找 QML 控件并 connect —— 这等于把 UI 结构硬编码进 C++,改个 id 就崩
最易被忽略的一点:分离不是靠目录结构或命名规范实现的,而是靠编译期依赖切断。只要 ui/MainWindow.h 里没出现任何业务类名、没包含业务头文件、不持有业务类非指针/引用类型的成员,才算真正分开了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











