OCP 的真实约束是已上线的稳定接口或抽象层不得修改,新功能须通过新增类型实现;如直接修改被多处调用的 ProductService.CalculateDiscount() 即违反该原则。

什么是 OCP 在 C# 中的真实约束
开放封闭原则(OCP)不是“不能改代码”,而是要求 已上线、被依赖的稳定接口或抽象层 保持不变,新功能通过新增类型来实现。一旦你直接修改了 ProductService.CalculateDiscount() 这类被多个模块调用的方法体,就违反了 OCP——哪怕只加了一行 if 判断。
用抽象基类 + 多态替换 if-else 分支
常见错误是把所有折扣逻辑塞进一个方法里,靠 switch (type) 或 if (product.Category == "VIP") 分流。这导致每次加新折扣类型都得改原方法,且单元测试要反复重跑全部分支。
- 定义抽象基类
DiscountStrategy,声明Calculate(decimal price)抽象方法 - 为每种策略新建类:
VipDiscountStrategy、SeasonalDiscountStrategy、BundleDiscountStrategy - 在运行时通过工厂或 DI 容器注入具体策略,而非硬编码判断逻辑
这样新增 BlackFridayDiscountStrategy 只需写新类+注册,不碰原有任何类的源码。
接口隔离 + 策略注册表避免修改核心调度器
当策略变多,有人会把所有策略类型写死在某个 DiscountDispatcher 的静态字典里,比如:strategies.Add("vip", new VipDiscountStrategy())。这看似没改业务逻辑,但注册本身仍是“修改”——违反 OCP。
- 定义
IDiscountStrategy接口,并约定实现类带无参构造函数或标记[Export] - 用
Assembly.GetExecutingAssembly().GetTypes()扫描所有实现类,自动注册(配合Activator.CreateInstance) - 或更稳妥地:让各策略类实现
IPlugin,启动时从plugins/目录动态加载程序集
注册逻辑从“写死在主程序里”变成“按约定发现”,这才是真正开放的扩展点。
泛型约束 + 扩展方法补足非侵入式增强
有些场景无法抽象出统一接口(比如对第三方类型 HttpClient 增加重试能力),此时不能动原始类,但可以安全扩展。
- 用
public static class HttpClientExtensions定义WithRetry(this HttpClient client, int maxRetries) - 返回包装后的
RetryableHttpClient(内部持有原实例),不改变HttpClient本身 - 若需注入行为,用
Func<httprequestmessage cancellationtoken task>></httprequestmessage>作为策略参数,而非修改原有发送逻辑
扩展方法本身不修改任何已有类型,调用方按需选用,老代码完全不受影响——这是 OCP 在工具层最轻量的落地方式。
真正难的不是写几个抽象类,而是识别哪些类/接口已经成了别人代码里的“公共契约”。一旦它们被跨项目引用,连加个 virtual 都可能引发下游编译失败。所以 OCP 的边界,往往由依赖关系图决定,而不是设计图。










