开放封闭原则(ocp)是设计约束而非c++语法特性,要求软件实体对扩展开放、对修改封闭;其核心是通过抽象基类+虚函数实现运行时多态,新增功能只需添加子类而不修改原有代码。

开放封闭原则不是C++语法特性,而是设计约束
开放封闭原则(OCP)在C++里没有对应的关键字或编译器检查,它完全依赖你对抽象、继承和多态的主动运用。如果你试图用class直接写死行为逻辑,那代码天然就违背OCP——新增功能就得改旧代码。
核心判断标准只有一条:当需求变化(比如增加一种新的日志输出方式),你是否能不修改已有类的源码,仅通过新增类+少量胶水代码就完成扩展?
用虚函数+基类指针实现行为开放
最常见也最稳妥的做法是定义抽象基类,把可变行为声明为virtual函数,让具体实现类负责重写。调用方只依赖基类指针或引用,完全不知道也不需要知道具体类型。
例如日志模块:
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& msg) = 0; // 纯虚,强制子类实现
};
<p>class ConsoleLogger : public Logger {
public:
void log(const std::string& msg) override { std::cout </p><p>class FileLogger : public Logger {
public:
void log(const std::string& msg) override { /<em> 写入文件 </em>/ }
};
</p>
业务代码中:
使用std::unique_ptr<logger></logger>或Logger*接收对象,新增NetworkLogger时,只需新写一个类,不碰ConsoleLogger或FileLogger的任何一行代码,也不改调用逻辑。
容易踩的坑:
- 忘记把析构函数声明为
virtual,导致delete基类指针时子类析构函数不被调用 - 把
log()写成非虚函数,子类重写只是隐藏而非覆盖,多态失效 - 在基类里塞太多具体实现(比如硬编码格式字符串),反而锁死了扩展路径
模板和策略模式也能满足OCP,但适用场景不同
如果扩展行为在编译期确定、且不需要运行时切换,template配合策略类更轻量:
template<typename strategy>
class Processor {
Strategy s_;
public:
void handle() { s_.execute(); }
};
<p>struct ConsoleStrategy { void execute() { std::cout to file / } };
</p></typename>
这时新增策略只需定义新结构体,不改Processor定义——也算OCP。但它和虚函数的根本区别在于:
- 虚函数支持运行时多态,适合配置驱动、插件式扩展
- 模板是编译期绑定,零运行时开销,但无法动态加载新策略
- 误用模板强行模拟运行时行为(比如用
if-else选模板实例)会破坏OCP本意
真正难的是识别“哪些部分该开放”
OCP失效往往不是因为不会写virtual,而是过早锁定变化点。比如把日志级别(INFO/WARN/ERROR)硬编码进log()参数类型,后续加DEBUG就得改函数签名;或者把时间戳格式写死在ConsoleLogger::log()里,换格式就得动这个类。
关键动作是:每次加新功能前,先问一句——这个变化会不会影响其他已有类的行为?如果答案是“会”,说明抽象层没切对位置,得重构基类接口,而不是往老类里塞if (type == NEW_TYPE)。
多数人卡在这一步:不是不会写多态,而是没想清楚什么才是稳定的契约。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











