我的 Qt 上位機(jī)項(xiàng)目 MVC 重構(gòu)日記
# 我的 Qt 上位機(jī)項(xiàng)目 MVC 重構(gòu)日記
日期: 2025-07-31
前言
今天我將把一個(gè)剛用 Qt Creator 17.0.0 + Qt 6.9.1 創(chuàng)建的 “Qt Widgets Application”——MyMonitorApp, 從 Qt Creator 默認(rèn)的文件組織方式(頭文件/源文件/界面文件三大 filter)改造成清晰的 MVC(Model–View–Controller)分層項(xiàng)目。
一、項(xiàng)目初始狀態(tài)
-
Qt 版本:Qt 6.9.1
-
Creator 版本:17.0.0
-
項(xiàng)目名稱:MyMonitorApp
-
磁盤目錄結(jié)構(gòu)(通過(guò)“文件系統(tǒng)”視圖看到):
MyMonitorApp/ ├── MyMonitorApp.pro ├── headers/ │ └── mainwindow.h ├── sources/ │ ├── main.cpp │ └── mainwindow.cpp └── forms/ └── mainwindow.ui -
IDE “項(xiàng)目”視圖(邏輯分組,不是真實(shí)目錄):
├── 頭文件 ├── 源文件 └── 界面文件
二、為什么要拆成 MVC?
-
職責(zé)分離
- Model:負(fù)責(zé)管理和存儲(chǔ)數(shù)據(jù)。
- View:只關(guān)心 UI 展示(
.ui+ 界面類實(shí)現(xiàn))。 - Controller:處理業(yè)務(wù)邏輯、和下位機(jī)的串口/網(wǎng)絡(luò)通信。
-
可維護(hù)性
- 功能模塊清晰,新同事可快速定位代碼。
-
可擴(kuò)展性
- 增加新功能只要在對(duì)應(yīng)層新增文件夾,不影響其他層。
三、重構(gòu)步驟
1. 在磁盤上創(chuàng)建分層目錄
cd MyMonitorApp
mkdir model view controller util
- model/:放
QAbstractItemModel子類 - view/:放所有
.ui+ 對(duì)應(yīng)的.h/.cpp - controller/:放業(yè)務(wù)邏輯與通訊類
- util/:放串口管理、日志、配置等輔助工具
2. 移動(dòng)文件
# View 層
mv headers/mainwindow.h view/
mv sources/mainwindow.cpp view/
mv forms/mainwindow.ui view/
# 根目錄下 main.cpp 保持不動(dòng)或按需放 controller/
此時(shí)磁盤目錄變?yōu)椋?/p>
MyMonitorApp/
├── model/
├── view/
│ ├── mainwindow.h
│ ├── mainwindow.cpp
│ └── mainwindow.ui
├── controller/
├── util/
├── main.cpp
└── MyMonitorApp.pro
3. 修改 MyMonitorApp.pro
打開(kāi)并編輯,將原來(lái)指向 headers/、sources/、forms/ 的條目全部替換成 MVC 目錄下的路徑:
QT += core gui widgets
CONFIG += c++17
- INCLUDEPATH += $$PWD/headers
+ INCLUDEPATH += $$PWD/model \
+ $$PWD/view \
+ $$PWD/controller \
+ $$PWD/util
- HEADERS += headers/mainwindow.h
- SOURCES += sources/main.cpp \
- sources/mainwindow.cpp
- FORMS += forms/mainwindow.ui
+ HEADERS += \
+ view/mainwindow.h \
+ model/DeviceDataModel.h \
+ controller/DeviceController.h
+ SOURCES += \
+ main.cpp \
+ view/mainwindow.cpp \
+ model/DeviceDataModel.cpp \
+ controller/DeviceController.cpp
+ FORMS += \
+ view/mainwindow.ui
小提醒:IDE “項(xiàng)目”視圖里的 “頭文件/源文件/界面文件” 只是 qmake filter,不影響真實(shí)目錄。
4. 在 Qt Creator 中驗(yàn)證
- 打開(kāi)項(xiàng)目:
MyMonitorApp.pro - 切換到 “文件系統(tǒng)” 視圖,確認(rèn)只剩下
model/、view/、controller/、util/。 - “構(gòu)建→清理項(xiàng)目 → 運(yùn)行 QMake → 編譯”
四、代碼骨架示例
1. Model
// model/DeviceDataModel.h
#pragma once
#include <QAbstractTableModel>
#include "DeviceItem.h"
class DeviceDataModel : public QAbstractTableModel {
Q_OBJECT
public:
explicit DeviceDataModel(QObject* parent = nullptr);
int rowCount(...) const override;
int columnCount(...) const override;
QVariant data(...) const override;
void appendData(const DeviceItem& item);
private:
QVector<DeviceItem> m_items;
};
2. Controller
// controller/DeviceController.h
#pragma once
#include <QObject>
#include "model/DeviceDataModel.h"
#include "util/SerialPortManager.h"
class DeviceController : public QObject {
Q_OBJECT
public:
DeviceController(DeviceDataModel* model, QObject* parent = nullptr);
private slots:
void onSerialDataReceived(const QByteArray& raw);
private:
DeviceDataModel* m_model;
SerialPortManager m_serial;
};
3. View
<!-- view/mainwindow.ui -->
<ui version="4.0">
<class>MainWindow</class>
<!-- … -->
</ui>
// view/mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_model = new DeviceDataModel(this);
ui->tableView->setModel(m_model);
m_ctrl = new DeviceController(m_model, this);
}
五、心得與建議
-
IDE 視圖 vs 文件系統(tǒng):
- “項(xiàng)目”視圖便于按文件類型快速定位;
- “文件系統(tǒng)”視圖才映射真實(shí)目錄結(jié)構(gòu)。
-
分層不分味:
- 小項(xiàng)目可以不嚴(yán)格三層都寫(xiě),按需增刪;
- 大項(xiàng)目建議在各層內(nèi)部再做二級(jí)模塊劃分。
-
版本管理:
- 重構(gòu)后別忘了更新
.gitignore(移除舊目錄引用)。
- 重構(gòu)后別忘了更新
重構(gòu)完畢后,MyMonitorApp 的目錄變得干凈整齊,邏輯分層清晰,后續(xù)新增功能或移植到嵌入式 Qt 時(shí)都省心不少。希望這篇日記對(duì)你也有幫助!

浙公網(wǎng)安備 33010602011771號(hào)