过大的类应按“变化原因”而非代码行数拆分,识别职责混杂信号(如多依赖、多初始化、多mock),依变化边界划接口再重构,通过依赖注入、回调、Context对象解耦,注意模板/inline/析构顺序陷阱。

过大的类不是“写得太多”,而是职责混杂。拆分的关键不是按代码行数切,而是按「变化原因」切——数据库改了要不要动日志逻辑?UI换风格会不会波及数据校验?如果答案是“会”,那它们就不该在一个类里。
识别违反单一职责的典型信号
这类问题往往在修改时才暴露,而不是编译时报错:
-
saveToDatabase()和sendEmailNotification()出现在同一个类的公有接口里 - 类头文件里同时包含
#include <mysql_driver.h>、#include <log4cpp/Category.hh>、#include <QtWidgets> - 构造函数里既打开数据库连接,又初始化 UI 控件,还读取配置文件
- 单元测试必须 mock 三类外部依赖(DB、网络、文件系统)才能跑通一个测试用例
按“变化边界”拆分:先划接口,再动实现
别一上来就剪代码。先问:这个类对外暴露的每个 public 方法,背后真正依赖的是什么?哪些方法共享同一套变化理由?
- 所有以
load、save、update开头的方法 → 归入DataAccessLayer类,只依赖数据库驱动和实体类 - 所有含
log、error、warn的方法 → 提炼为AppLogger,只接收结构化日志消息,不关心输出到文件还是 syslog - 所有返回
QWidget*或调用QMessageBox::的方法 → 移到UICoordinator,与业务逻辑零耦合 - 原类中纯计算逻辑(比如
calculateTotalPrice()、validateOrder())→ 留在重构后的OrderProcessor,它只操作Order实体,不碰 I/O
拆分后如何安全通信?避免新耦合
拆完发现类之间调来调去更乱?说明用了错误的协作方式:
立即学习“C++免费学习笔记(深入)”;
- 禁止让
DataAccessLayer直接 newAppLogger—— 改用构造函数注入或工厂回调 - 不要在
OrderProcessor里写logger->info("price calculated"),而是让它抛出std::optional<Error>或返回状态码,由上层决定是否记日志 - UI 层调用业务逻辑时,传入
std::function<void(const std::string&)>作为通知回调,而不是把UICoordinator*塞进去 - 如果多个拆分后的类需要共享状态(如当前用户权限),抽成独立的
Context对象,通过 const 引用传入,不提供 setter
模板类和内联函数容易被忽略的陷阱
拆分过程中若涉及模板或高频调用函数,稍不注意就会破坏封装或引发 ODR 违规:
- 把原本在大类里的
template<typename T> void serialize(T& obj)拆进新头文件时,必须确保整个定义(声明+实现)都在同一个.h文件里,否则链接失败 - 原类中定义的 inline 成员函数(比如
inline int getId() const { return id_; }),拆出去后仍需保持inline或移到.inl文件并显式包含 - 若拆出的类带模板参数(如
Repository<User>),不要在.cpp里特化,而是在使用点(比如main.cpp)包含完整定义
最常被跳过的一步:检查析构顺序。当多个职责类持有对方的裸指针或 shared_ptr 时,循环依赖不会立刻报错,但对象销毁顺序错乱会导致 use-after-free —— 用 std::weak_ptr 或明确所有权链(谁 new 谁 delete)来守住这条线。


















