写C++十年,我现在怎么设计类和模块?(附真实项目结构)

发布于:2025-09-06 ⋅ 阅读:(16) ⋅ 点赞:(0)

博主介绍:程序喵大人

最近爆肝了一个网站(希加加职业发展平台),可以对简历进行评估、并且能够根据你的简历内容进行面试押题预测,分享给大家。各位有需要的同学也可以去网站上实践体验一下,希望能帮助到你~

写 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!


网站公告

今日签到

点亮在社区的每一天
去签到