当前位置: 首页 > news >正文

用分层架构打造单体应用

在桌面端、工业控制、嵌入式 HMI 等领域,Qt/C++ 单体应用依然是主流。但很多开发者一听到“单体”,脑海中浮现的就是“大泥球”——代码耦合严重、牵一发而动全身、新人接手如读天书。其实,单体 ≠ 混乱。今天,我们就来揭秘如何用经典的 分层架构(Layered Architecture),为你的 Qt/C++ 项目打下坚实、清晰、易维护的根基。

构建一个结构良好、易于维护的 Qt/C++ 单体应用,关键在于良好的架构设计、清晰的代码组织、有效的解耦和一致的编码规范


一、为什么选择分层架构?

在微服务大行其道的今天,分层架构依然是单体应用的黄金标准,尤其适合:

  • 中小型桌面应用程序
  • 对启动速度和资源占用敏感的场景
  • 团队规模较小,希望降低运维复杂度
  • 项目初期快速迭代验证

分层架构的核心价值在于:

  • 关注点分离 —— UI、逻辑、数据各司其职
  • 高内聚低耦合 —— 修改界面不影响业务逻辑
  • 易于测试 —— 业务层可独立单元测试
  • 便于维护与扩展 —— 新功能按层添加,结构清晰
  • 团队协作友好 —— 前端、后端开发人员可并行工作

二、四层经典结构:Qt/C++ 单体应用的骨架

推荐采用以下四层结构构建你的 Qt 应用:

┌──────────────────────────────────────┐
│          表现层 (Presentation)       │ ← 用户看得见、摸得着的界面
├──────────────────────────────────────┤
│        业务逻辑层 (Business Logic)   │ ← 应用的大脑,处理核心规则
├──────────────────────────────────────┤
│        数据访问层 (Data Access)      │ ← 与数据库、文件、网络打交道
├──────────────────────────────────────┤
│          公共工具层 (Common)         │ ← 日志、配置、工具函数等基础设施
└──────────────────────────────────────┘

2.1 第一层:表现层(UI Layer)—— 只负责“展示”和“传达”

职责:

  • 渲染界面(Widgets 或 QML)
  • 接收用户输入(点击、输入、拖拽)
  • 将用户操作“传达”给业务层,不处理任何业务逻辑!

Qt 实现技巧:

// MainWindow.h —— 纯粹的 UI 容器
class MainWindow : public QMainWindow {Q_OBJECT
public:explicit MainWindow(IUserService* userService, QWidget *parent = nullptr);private slots:void onLoginButtonClicked(); // 槽函数:只负责“传达”private:Ui::MainWindow *ui;IUserService* m_userService; // 依赖注入!
};
// MainWindow.cpp
void MainWindow::onLoginButtonClicked() {QString username = ui->lineEditUsername->text();QString password = ui->lineEditPassword->text();// 正确做法:把数据交给业务层处理try {User user = m_userService->login(username, password);emit userLoggedIn(user); // 通知其他组件accept();} catch (const LoginException& e) {QMessageBox::warning(this, "登录失败", e.what());}// 错误做法:在这里写数据库查询、密码加密、权限判断...
}

关键点:

  • UI 类中不出现 QSqlQueryQFile、复杂算法
  • 使用 依赖注入 传入业务服务接口
  • 信号槽机制 与业务层通信,避免直接调用具体实现

2.2 第二层:业务逻辑层(Service Layer)—— 应用真正的“大脑”

职责:

  • 实现核心业务规则(如:用户登录验证、订单计算、数据校验)
  • 协调多个数据访问操作
  • 处理事务、异常、日志
  • 不关心数据从哪来、界面长什么样

Qt/C++ 实现技巧:

// IUserService.h —— 定义接口,解耦实现
class IUserService {
public:virtual ~IUserService() = default;virtual User login(const QString& username, const QString& password) = 0;virtual void logout() = 0;
};// UserService.h —— 具体实现
class UserService : public QObject, public IUserService {Q_OBJECT
public:explicit UserService(IUserRepository* repo, QObject* parent = nullptr);User login(const QString& username, const QString& password) override;void logout() override;signals:void userLoggedIn(const User& user);void userLoggedOut();private:IUserRepository* m_userRepository;Logger* m_logger;
};
// UserService.cpp
User UserService::login(const QString& username, const QString& password) {m_logger->info("用户尝试登录: " + username);if (username.isEmpty() || password.isEmpty()) {throw LoginException("用户名或密码不能为空");}User user = m_userRepository->findByUsername(username); // 调用数据层if (user.isNull() || !verifyPassword(password, user.passwordHash())) {throw LoginException("用户名或密码错误");}if (!user.isActive()) {throw LoginException("用户已被禁用");}m_logger->info("用户登录成功: " + user.name());emit userLoggedIn(user);return user;
}

关键点:

  • 使用 纯虚类接口 (IUserService) 实现依赖倒置
  • 业务类可继承 QObject 以便使用 信号槽
  • 依赖的数据层对象通过 构造函数注入
  • 抛出自定义异常,由 UI 层捕获并友好提示

2.3 第三层:数据访问层(Repository Layer)—— 专注“CRUD”

职责:

  • 封装数据库操作(增删改查)
  • 对象-关系映射(ORM)
  • 管理数据库连接、事务
  • 不包含任何业务规则!

Qt 实现技巧:

// IUserRepository.h
class IUserRepository {
public:virtual ~IUserRepository() = default;virtual User findByUsername(const QString& username) = 0;virtual bool save(const User& user) = 0;virtual bool deleteById(int id) = 0;
};// UserRepository.h
class UserRepository : public IUserRepository {
public:explicit UserRepository(QSqlDatabase* db);User findByUsername(const QString& username) override;bool save(const User& user) override;private:QSqlDatabase* m_db;
};// UserRepository.cpp
User UserRepository::findByUsername(const QString& username) {QSqlQuery query(*m_db);query.prepare("SELECT id, name, password_hash, is_active FROM users WHERE username = ?");query.addBindValue(username);if (query.exec() && query.next()) {User user;user.setId(query.value("id").toInt());user.setName(query.value("name").toString());user.setPasswordHash(query.value("password_hash").toString());user.setActive(query.value("is_active").toBool());return user;}return User(); // or throw, depending on your policy
}

关键点:

  • 使用 Repository 模式 封装数据访问
  • 数据库操作集中管理,避免 SQL 散落在各处
  • 返回业务模型对象User),而非 QSqlRecord
  • 可轻松替换为 Mock 实现用于单元测试

2.4 第四层:公共工具层(Common Layer)—— 基础设施大本营

职责:

  • 日志系统(Logger)
  • 配置管理(ConfigManager)
  • 工具函数(字符串处理、文件操作、加密)
  • 自定义异常类
  • 常量定义
// Logger.h
class Logger {
public:static void info(const QString& message);static void error(const QString& message);static void setLogFile(const QString& path);
};// ConfigManager.h
class ConfigManager {
public:static QString getDatabasePath();static int getMaxRetryCount();static void setLanguage(const QString& lang);
};

三、层与层之间如何优雅通信?

推荐方式:

  1. 依赖注入(构造函数 / Setter)
    → 表现层 → 业务层 → 数据层

  2. Qt 信号与槽(跨层通知)

    // 业务层发出信号
    connect(m_userService, &UserService::userLoggedIn,this, &MainWindow::updateWelcomeLabel);
    
  3. 接口调用(同步请求/响应)

    User user = m_userService->login(username, password); // 同步
    

避免:

  • 表现层直接调用 QSqlQuery
  • 业务层 #include "MainWindow.h"
  • 数据层抛出 QMessageBox

四、可测试性

得益于清晰的分层和接口抽象:

  • 业务逻辑层 → 可脱离 UI 和数据库,用 Mock 对象进行单元测试
  • 数据访问层 → 可用内存数据库(如 SQLite in-memory)测试
  • 表现层 → 可用 QTest 模拟用户操作进行UI自动化测试
// TestUserService.cpp (使用 Qt Test)
void TestUserService::testLoginWithInvalidPassword() {MockUserRepository mockRepo;EXPECT_CALL(mockRepo, findByUsername("alice")).WillOnce(Return(User("alice", "hashed_wrong")));UserService service(&mockRepo);QVERIFY_EXCEPTION_THROWN(service.login("alice", "wrong"), LoginException);
}

五、项目结构建议(CMake + Qt)

MyQtApp/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   ├── common/
│   │   ├── Logger.h/cpp
│   │   └── ConfigManager.h/cpp
│   ├── presentation/
│   │   ├── MainWindow.h/cpp
│   │   └── controllers/
│   │       └── UserController.h/cpp
│   ├── business/
│   │   ├── services/
│   │   │   ├── UserService.h/cpp
│   │   │   └── IUserService.h
│   │   └── models/
│   │       └── User.h/cpp
│   └── data/
│       ├── repositories/
│       │   ├── UserRepository.h/cpp
│       │   └── IUserRepository.h
│       └── DatabaseManager.h/cpp
└── tests/└── TestUserService.cpp

六、总结:分层不是枷锁,而是翅膀

分层架构不是增加复杂度,而是用结构化的思维管理复杂度。对于 Qt/C++ 单体应用:

“前期多花 10% 的时间设计架构,后期节省 200% 的维护成本。”

当你下次新建 Qt 项目时,不妨从这四层开始:

  1. 表现层 —— 画界面、传消息
  2. 业务层 —— 定规则、做计算
  3. 数据层 —— 存数据、取数据
  4. 公共层 —— 打基础、供弹药

文章转载自:

http://PivwqKbq.pxspq.cn
http://6yvTc0bX.pxspq.cn
http://O2tFK0rR.pxspq.cn
http://iXCTJi9Z.pxspq.cn
http://2jHcqLFF.pxspq.cn
http://fa3n7d0q.pxspq.cn
http://bh3ZVoDW.pxspq.cn
http://8kikGBpZ.pxspq.cn
http://MD4GuAB1.pxspq.cn
http://jgyTEqCZ.pxspq.cn
http://vrdTtnr8.pxspq.cn
http://HUvqf2ag.pxspq.cn
http://XXPkpAf6.pxspq.cn
http://WGxt5cd3.pxspq.cn
http://5jLSZh3G.pxspq.cn
http://vB3NTCs4.pxspq.cn
http://LTb2Rbxz.pxspq.cn
http://3vdXhaI2.pxspq.cn
http://SedxmLQT.pxspq.cn
http://jFy6xJ0v.pxspq.cn
http://QVyGT8kr.pxspq.cn
http://7RdIWcvI.pxspq.cn
http://d7WxyMwl.pxspq.cn
http://zwCwPcqy.pxspq.cn
http://3qAdI6au.pxspq.cn
http://Y6OYMeNB.pxspq.cn
http://kMuTW1h2.pxspq.cn
http://gLMZSHCY.pxspq.cn
http://lqozXz4h.pxspq.cn
http://vwmi91pc.pxspq.cn
http://www.dtcms.com/a/377628.html

相关文章:

  • TCP 拥塞控制设计空间课程要点总结
  • 工业网络融合:DEVICENET转PROFINET网关在汽车总装车间的应用
  • AI当调色总监用,合成图一秒融入,甲方看了都说绝
  • 深入浅出CRC校验:从数学原理到单周期硬件实现 (1) 初始CRC校验
  • 基于esp32c3 rust embassy 的墨水屏程序
  • 实战:用 Python 搭建 MCP 服务 —— 模型上下文协议(Model Context Protocol)应用指南
  • 亚马逊关键词引流:从手动操作到智能优化的全流程攻略
  • Windows 本地组策略重置标准操作程序 (SOP)
  • tp5.0如何配置session保存到文件里,方便删除
  • Linux D-Bus 详解
  • 嵌入式学习笔记.嵌入式系统
  • Redis 与分布式事务:最终一致性的实践艺术
  • Mac M 系列芯片 YOLOv8 部署教程(CPU/Metal 后端一键安装)
  • Java 中String类的常用方法
  • TENGJUN防水TYPE-C连接器:立贴结构与IPX7防护的精密融合
  • 和照片互动?NAS 部署 AI 智能相册,瀑布流+网格双布局!
  • 网络原理——传输层协议TCP基本认识
  • ETF提供流动性 DAT提供创造性
  • 深入理解C++多态:从概念到实现原理
  • ​Premiere Pro 2024 v24.0.0.58 怎么安装?详细教程(附安装包)
  • 关于调用第三方API服务(New API)等出现被Cloudfare拦截问题解决
  • 用 Python UTCP 直调 HTTP、CLI、MCP……
  • 在 QML 中,clip: true 属性对于 AnimatedImage 裁剪无效的问题通常是由于以下原因及解决方案
  • 硬件开发_基于STM32单片机的智能投送小车
  • 开始 ComfyUI 的 AI 绘图之旅-Flux.1文生图(全网首发,官网都没有更新)(七)
  • c++模板的使用
  • docker部署openlist配置SLL证书
  • 设计模式-策略模式深度分析
  • 洛谷P3405 [USACO16DEC] Cities and States S (哈希表法)详解
  • Vue3纯前端同源跨窗口通信移动AGV小车