架构设计基础:分层架构、MVC/MVP/MVVM模式、C++项目目录结构规范
聊到架构设计,很多刚入行的朋友容易一头扎进设计模式里出不来。其实我个人觉得,比设计模式更先要搞清楚的,是架构的骨架——也就是分层和模式。骨架搭对了,后面填肉(写代码)才不容易散架。今天我们就来聊聊这三个核心话题:分层架构、MVC/MVP/MVVM 模式,以及 C++ 项目目录该怎么组织。
一、分层架构:把代码“隔开”的艺术
分层架构,说白了就是让不同职责的代码待在不同的房间里。你想想看,如果所有代码都堆在一个文件里,那改一个功能可能牵动全身,谁都不敢动。我早期做嵌入式项目时就吃过这个亏——一个 5000 行的 main.cpp,改个日志打印都能编译出链接错误……嗯,后来我彻底重构了。
经典的分层一般是三层:
- 表现层(UI):负责和用户打交道,显示界面、接收输入。
- 业务逻辑层(Service / Domain):核心算法、业务规则、状态管理。
- 数据访问层(Persistence / Repository):读写数据库、文件、网络等。
每一层只依赖它的下一层,不能跨层调用。举个例子:UI 层不能直接操作数据库,必须通过业务层中转。这样做的好处是——哪天你想把数据库从 MySQL 换成 Redis,只需要改数据访问层,业务层和 UI 层完全不用动。
我在项目中遇到过一种情况:团队为了“灵活”,让业务层直接返回 UI 层需要的 DTO 对象。结果 UI 层一改,业务层也得跟着改,分层形同虚设。所以记住:层与层之间用接口或抽象类通信,别让具体实现暴露出去。
二、MVC / MVP / MVVM:三种“分家”的姿势
这三兄弟都是分层架构的具体实现,但侧重点不同。我分别说说它们的核心区别,以及我在实际项目中的选择。
1. MVC(Model-View-Controller)
MVC 是最经典的。Model 负责数据和业务逻辑,View 负责显示,Controller 负责接收用户输入并协调 Model 和 View。它的流程是:用户操作 View → Controller 更新 Model → Model 通知 View 刷新。
但 MVC 有个坑:View 和 Model 之间容易产生直接依赖。我记得有一次,我们团队用 Qt 做桌面应用,View 直接绑定了 Model 的成员变量,结果 Model 一改,View 编译报错一大堆。后来我们强制要求 View 只能通过 Controller 访问 Model,才把耦合降下来。
2. MVP(Model-View-Presenter)
MVP 把 Controller 换成了 Presenter。区别在于:View 和 Model 完全隔离,所有交互都通过 Presenter 进行。View 只负责显示和事件转发,Presenter 负责业务逻辑和状态管理。
我曾在 Windows 桌面应用(MFC)中用过 MVP。当时界面逻辑很复杂,但测试起来特别爽——因为 Presenter 不依赖具体的 View 实现,只需要一个接口。写单元测试时,我 mock 一个 View 接口,就能把业务逻辑测个遍。
// 一个简单的 MVP 示例(C++ 伪代码)
class IView {
public:
virtual void ShowData(const std::string& data) = 0;
virtual ~IView() = default;
};
class Presenter {
public:
Presenter(IView* view, Model* model) : view_(view), model_(model) {}
void OnUserClick() {
std::string result = model_->Process();
view_->ShowData(result);
}
private:
IView* view_;
Model* model_;
};
3. MVVM(Model-View-ViewModel)
MVVM 是 MVP 的进化版,核心是 数据绑定。ViewModel 暴露可观察的属性,View 自动订阅这些属性的变化。这样 View 几乎不需要写代码,只需要在 UI 里绑定 ViewModel 的属性即可。
MVVM 在 C++ 中实现起来稍微麻烦一些,因为 C++ 没有原生的数据绑定机制。不过可以用 Qt 的 Q_PROPERTY 或 Boost.Signals2 来模拟。我个人觉得,如果项目用了 Qt Quick(QML),MVVM 是天然的选择;如果是纯 Widget 项目,MVP 反而更可控。
三、C++ 项目目录结构规范
架构模式定好了,代码往哪放?目录结构就是项目的“物理架构”。我见过太多 C++ 项目,所有源文件堆在一个 src 目录下,头文件和实现混在一起,找文件全靠搜索。嗯,这种项目维护起来真的很痛苦。
下面是我个人比较推荐的一种目录结构,适用于中型以上 C++ 项目:
project_root/
├── 3rdparty/ # 第三方库(只放头文件和静态库)
├── build/ # 构建输出目录(CMake 构建)
├── cmake/ # CMake 模块和工具脚本
├── docs/ # 文档(设计文档、API 文档)
├── include/ # 公共头文件(对外暴露的接口)
│ └── module_name/
│ ├── public_api.h
│ └── ...
├── src/ # 源代码
│ ├── module_a/ # 模块 A
│ │ ├── impl/ # 内部实现(不对外暴露)
│ │ ├── test/ # 模块 A 的单元测试
│ │ └── CMakeLists.txt
│ ├── module_b/
│ │ ├── impl/
│ │ ├── test/
│ │ └── CMakeLists.txt
│ └── main.cpp # 主入口
├── test/ # 集成测试 / 端到端测试
├── tools/ # 辅助工具(代码生成、脚本)
├── .clang-format # 代码格式化配置
├── .gitignore
└── CMakeLists.txt # 顶层 CMake 文件
几个关键点:
- 头文件与实现分离:公共头文件放在
include/,内部实现放在src/模块名/impl/。这样外部使用者只看到接口,看不到实现细节。 - 模块化:每个模块有自己的目录,包含独立的 CMakeLists.txt。模块之间通过接口依赖,不要直接引用其他模块的内部头文件。
- 测试与源码同级:每个模块的单元测试放在模块目录下的
test/中,方便开发者快速定位和运行。 - 第三方库统一管理:所有第三方库放在
3rdparty/,不要散落在系统路径中。这样换机器或 CI 环境时,只需要拷贝这个目录。
3rdparty 和 src 混在一起,结果升级第三方库时,不小心把旧版本的头文件删了,导致整个项目编译失败。所以第三方库一定要独立出来,并且用版本号或 git submodule 管理。
四、一张图看懂本章核心
下面我用一张 SVG 图来总结分层架构、MVC/MVP/MVVM 的关系,以及它们如何映射到 C++ 项目目录中。这张图我画得比较简洁,但核心逻辑都在里面了。
五、总结与个人体会
架构设计没有银弹。分层架构、MVC/MVP/MVVM 都是工具,关键看你的项目场景。我个人习惯:
- 如果是桌面应用(Qt Widgets),我倾向 MVP,因为 Presenter 容易测试,View 的替换成本低。
- 如果是移动端或 QML 项目,MVVM 更自然,数据绑定能省很多代码。
- 如果是后端服务,分层架构足够,配合依赖注入框架(比如 Boost.DI)效果更好。
至于目录结构,我建议团队在项目初期就定好规范,并且用 CMake 的 add_subdirectory 强制模块化。别等到代码写到 10 万行再重构——那时候成本就高了。
好了,这一章的内容就到这里。下一章我们会深入聊聊设计模式在 C++ 中的具体应用,到时候我会拿几个真实项目中的例子来拆解。嗯,敬请期待。
公众号:蓝海资料掘金营,微信 deep3321