写C++十年,我现在怎么设计类和模块?(附真实项目结构)
博主介绍:程序喵大人
- 35 - 资深C/C++/Rust/Android/iOS客户端开发
- 10年大厂工作经验
- 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
- 《C++20高级编程》《C++23高级编程》等多本书籍著译者
- 更多原创精品文章,首发gzh,见文末
- 👇👇记得订阅专栏,以防走丢👇👇
😉C++基础系列专栏
😃C语言基础系列专栏
🤣C++大佬养成攻略专栏
🤓C++训练营
👉🏻个人网站
最近爆肝了一个网站(希加加职业发展平台),可以对简历进行评估、并且能够根据你的简历内容进行面试押题预测,分享给大家。各位有需要的同学也可以去网站上实践体验一下,希望能帮助到你~
写 C++ 第一年,我觉得“写得能跑就行”; 第三年,我开始讲究“封装、继承、多态”; 第十年,我才明白:设计一个好的类和模块,远比把代码写对难得多。
今天我想和你聊聊:十年 C++ 开发之后,我在真实项目中,是如何设计类与模块的——不是学术讲解,是踩过坑、落过地、真在维护的经验总结。
1. 刚工作时的“错误设计”长啥样?
来看看我早期的写法:
class VideoDecoder {
public:bool open(const std::string& filePath);Frame decodeNextFrame();void close();
private:AVFormatContext* formatCtx;AVCodecContext* codecCtx;...
};
这段代码看起来挺顺,但项目一大,就暴露出不少问题:
- 类太臃肿:既处理文件,又管理内存,还做解码逻辑,职责太多;
- 强耦合:代码依赖 FFmpeg,写死了平台细节,换库得重构;
- 不好测:你得准备视频文件才能测,没法写纯单元测试;
- 不好扩展:你想支持网络流?得改原来的类;想加日志?又得动这个类……
这类“面条式类”,一开始写得爽,后面维护地狱。
2. 后来我怎么设计?
原则一:每个类只做“一件事”
我现在设计类,第一件事就是问自己:“这个类的职责到底是什么?”
比如“解码视频”听上去是一件事,其实包含了很多职责:
- 打开资源(文件、本地、网络)
- 初始化解码器(平台相关)
- 解码逻辑(帧处理)
- 日志记录、错误处理
所以我现在会拆成多个类,每个类只做一件事:
class VideoSource {
public:virtual bool open(const std::string& path) = 0;virtual std::vector<uint8_t> read() = 0;virtual void close() = 0;
};class VideoDecoder {
public:void setInput(std::shared_ptr<VideoSource> input);Frame decode();
};
VideoSource
负责“从某种来源读取视频数据”,可以有多个实现:FileVideoSource
(读取文件)HttpVideoSource
(读取网络)
VideoDecoder
负责“如何解码”,它不关心你从哪来的数据,也不关心你怎么管理资源。
这种设计的好处:
- ✅ 测试方便:写个
MockVideoSource
就能测VideoDecoder
; - ✅ 可扩展:以后支持新来源,不用动解码器代码;
- ✅ 高内聚、低耦合:逻辑分明,维护轻松。
原则二:用接口 + 工厂解耦模块之间的依赖
在真实项目中,模块之间要解耦,我一般通过抽象接口 + 工厂来处理:
// 接口定义
class IEncoder {
public:virtual void init(const EncoderConfig& config) = 0;virtual void encode(Frame frame) = 0;virtual void close() = 0;
};// 不同实现
class H264Encoder :public IEncoder { ... };
class VP9Encoder :public IEncoder { ... };// 工厂
std::shared_ptr<IEncoder> CreateEncoder(const std::string& codec) {if (codec == "h264") return std::make_shared<H264Encoder>();if (codec == "vp9") return std::make_shared<VP9Encoder>();throw std::runtime_error("Unsupported codec");
}
这样调用方只依赖 IEncoder
接口,不关心具体实现:
auto encoder = CreateEncoder("h264");
encoder->init(cfg);
encoder->encode(frame);
易扩展 + 可测试 + 高可维护性,一石三鸟。
3. 我现在真实项目的模块结构是这样的:
以下是我们一个音视频处理系统的简化目录结构:
/src
├── common/ # 工具类、日志、配置、线程池等
├── interface/ # 对外暴露的 API 接口(HTTP/gRPC)
├── pipeline/ # 数据处理主流程控制模块
├── decoder/ # 解码模块
│ ├── ivideo_decoder.h
│ └── ffmpeg_decoder.cpp
├── encoder/ # 编码模块
├── io/ # 文件/网络输入输出
├── tests/ # 所有模块的单元测试
└── main.cpp # 启动程序
每个模块内部再用子模块细分,所有模块间只通过接口通信,没有环状依赖。我们用了 CMake 的 add_subdirectory
管理每个模块,所有模块可以独立编译测试。
总结我的类与模块设计习惯
原则 | 说明 |
---|---|
职责单一 | 一个类只做一件事 |
接口优先 | 抽象优先于实现 |
组合优于继承 | 减少复杂继承链 |
依赖注入 | 模块依赖通过参数传入 |
高内聚,低耦合 | 模块边界清晰,可独立使用 |
写给每一个 C++ 同行的话:
很多人问:“为什么我总感觉项目写着写着就烂掉了?”
我的回答是:项目结构和类设计,从第一天就决定了它能活多久。
写清晰的类,不是炫技,是对未来自己的负责。 划清模块边界,不是繁琐,是为了团队后面几年的平稳推进。
类是局部的架构,模块是全局的工程,设计好它们,是每一个C++开发者最重要的“重构功课”。
码字不易,欢迎大家点赞,关注,评论,谢谢!
👉 C++训练营
一个专为校招、社招3年工作经验的同学打造的 1v1 项目实战训练营,量身定制学习计划、每日代码review,简历优化,面试辅导,已帮助多名学员获得大厂offer!