当前位置: 首页 > ds >正文

设计模式-开放封闭原则

开放封闭原则

什么是开放封闭原则?

开放封闭原则是 SOLID 原则中的第二个字母 "O",由伯特兰·迈耶 (Bertrand Meyer) 在其著作《面向对象软件构造》中提出。它的核心思想是:

软件实体(类、模块、函数等)应该对于扩展是开放的,对于修改是封闭的。 (Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.)

这句话听起来有点矛盾,我们来拆解一下:

  • 对于扩展是开放的 (Open for extension): 这意味着当软件需要增加新的功能或行为时,我们应该能够通过添加新的代码来实现,而不是修改已有的、经过测试的代码。

  • 对于修改是封闭的 (Closed for modification): 这意味着一旦一个模块或类的核心功能开发完成并通过测试,我们应该尽量避免去修改它已有的代码。因为修改已有的代码可能会引入新的 bug,影响到依赖这个模块的其他部分。

为什么这个原则很重要?

遵守开放封闭原则可以带来以下显著的好处:

  1. 提高系统的稳定性和可靠性: 通过不修改已有的、稳定的代码,可以减少引入新错误的风险。新的功能通过新的代码实现,即使新代码有问题,影响范围也相对可控。

  2. 增强系统的可维护性: 当需要增加新功能时,开发人员不需要去理解和修改复杂的旧代码,只需要关注如何编写新的扩展代码,降低了维护成本。

  3. 提高系统的可复用性: 设计良好的、对修改封闭的模块更容易被其他系统或项目复用。

  4. 促进系统的灵活性和可扩展性: 系统能够更容易地适应需求的变化,因为添加新功能就像“插拔”组件一样。

  5. 降低回归测试的成本: 由于核心代码未被修改,回归测试的范围可以更集中在新添加的扩展部分。

如何实现开放封闭原则?

实现开放封闭原则的关键在于抽象化多态。通常可以通过以下方式来实现:

  1. 使用抽象类和接口:

    • 定义稳定的抽象层(接口或抽象类),封装变化的部分。

    • 具体的实现类继承抽象类或实现接口,从而实现扩展。

    • 客户端代码依赖于抽象层,而不是具体的实现类。

  2. 使用参数化行为(例如策略模式、模板方法模式):

    • 将可变的行为抽象成策略或模板中的可变步骤,允许通过传入不同的参数或实现不同的子类来改变行为。

  3. 使用钩子方法 (Hook Methods) 或回调机制:

    • 在稳定的框架代码中预留“钩子”,允许通过实现这些钩子来扩展功能。

  4. 依赖注入 (Dependency Injection) 和控制反转 (Inversion of Control):

    • 通过将依赖关系从代码内部移到外部配置或容器管理,使得在不修改原有代码的情况下替换或增加依赖成为可能。

举个例子:

假设我们有一个图形编辑器,需要绘制不同的形状(如圆形、矩形)。

不好的设计 (违反 OCP):

// 反例:违反开放封闭原则
class GraphicEditor {public void drawShape(Object shape) {if (shape instanceof Circle) {drawCircle((Circle) shape);} else if (shape instanceof Rectangle) {drawRectangle((Rectangle) shape);}// 当需要增加新的形状(如三角形)时,必须修改这里的 if-else 结构// else if (shape instanceof Triangle) {//     drawTriangle((Triangle) shape);// }}
​private void drawCircle(Circle c) {System.out.println("Drawing a Circle");}
​private void drawRectangle(Rectangle r) {System.out.println("Drawing a Rectangle");}// private void drawTriangle(Triangle t) { ... }
}
​
class Circle { /* ... */ }
class Rectangle { /* ... */ }
// class Triangle { /* ... */ }

在这个例子中,GraphicEditor 类直接依赖于具体的形状类。每当需要支持一种新的形状时,都必须修改 drawShape 方法中的 if-else 逻辑。这违反了“对修改封闭”的原则。

好的设计 (遵循 OCP):

// 改进:遵循开放封闭原则
​
// 1. 定义抽象形状 (Shape) - 抽象层
interface Shape {void draw();
}
​
// 2. 具体形状实现抽象 (Concrete Shapes)
class Circle implements Shape {@Overridepublic void draw() {System.out.println("Drawing a Circle");}
}
​
class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("Drawing a Rectangle");}
}
​
// 3. 图形编辑器依赖于抽象 (GraphicEditor)
class GraphicEditor {// 依赖于抽象 Shape 接口public void drawShape(Shape shape) {shape.draw(); // 调用抽象方法,具体行为由传入的 Shape 对象决定}
}
​
// 当需要增加新的形状时,例如三角形:
class Triangle implements Shape {@Overridepublic void draw() {System.out.println("Drawing a Triangle");}
}
​
// 客户端使用
public class Client {public static void main(String[] args) {GraphicEditor editor = new GraphicEditor();
​Shape circle = new Circle();Shape rectangle = new Rectangle();Shape triangle = new Triangle(); // 新增的形状
​editor.drawShape(circle);    // 输出: Drawing a Circleeditor.drawShape(rectangle); // 输出: Drawing a Rectangleeditor.drawShape(triangle);  // 输出: Drawing a Triangle  <-- 无需修改 GraphicEditor 类
​}
}

在这个改进的设计中:

  • 我们定义了一个 Shape 接口作为抽象层。

  • Circle 和 Rectangle 是 Shape 接口的具体实现。

  • GraphicEditor 的 drawShape 方法接收一个 Shape 类型的参数,并调用其 draw() 方法。它不关心具体的形状是什么。

现在,如果我们需要增加一个新的形状,比如 Triangle:

  1. 我们只需要创建一个新的 Triangle 类并实现 Shape 接口。

  2. GraphicEditor 类的代码完全不需要修改。 它对于新的形状是“开放”的(可以通过添加新的 Shape 实现来扩展),对于已有的绘制逻辑是“封闭”的。

这就是开放封闭原则的威力。

总结:

开放封闭原则是面向对象设计中一个非常核心且重要的原则。它的目标是通过抽象来构建一个稳定的、不易被修改的核心系统,同时又能灵活地通过添加新的代码来扩展系统的功能。实现 OCP 的关键在于识别系统中可能变化的部分,并将这些变化封装在抽象之后,使得系统的其他部分依赖于这个稳定的抽象。

虽然在实际开发中,完全做到“对修改封闭”有时比较困难,甚至在某些情况下,适度的修改是必要的。但开放封闭原则提供了一个重要的设计目标和方向,引导我们编写出更健壮、更灵活、更易于维护的软件系统。

http://www.xdnf.cn/news/9315.html

相关文章:

  • 多相电机驱动控制学习(1)——基于双dq坐标系的六相PMSM驱动控制
  • C++23 新成员函数与字符串类型的改动
  • 算法竞赛中的基本数论
  • 实时技术对比:SSE vs WebSocket vs Long Polling
  • 分布式光伏接入引起农村电压越限,如何处理?
  • Vue中van-stepper与input值不同步问题及解决方案
  • 如何将联系人从 Android 传输到 PC(正确步骤)
  • 哈尔滨云前沿服务器托管,服务器租用
  • influxdb时序数据库
  • 如何制作全景VR图?
  • Linux基础I/O【文件理解与操作】
  • nt!MiInitializeSystemCache函数分析之PointerPte->u.List.NextEntry的由来
  • 深度解析 K8S Pod 控制器,从原理到企业实践
  • [ Qt ] | 常见控件(二): window相关
  • 字符串day7
  • 线上 VR 展会:独特魅力与显著特质
  • 新增 git submodule 子模块
  • 安全接口设计:筑牢对外接口的安全防线
  • 企业im怎么选? BeeWorks -安全的企业内部通讯软件
  • 设计模式-单一职责原则
  • (14)JVM弹性内存管理
  • 【自用资源分享】Protocol Buffers 构建脚本: 支持生成 ​C++、Go、Python、Java 的 Protobuf 和 gRPC 代码
  • Leetcode-5 好数对的数目
  • 全局事务标识符
  • SPSS跨域分类:自监督知识+软模板优化
  • Ubuntu 下搭建ESP32 ESP-IDF开发环境,并在windows下用VSCode通过SSH登录Ubuntu开发ESP32应用
  • WordPress免费网站模板下载
  • 【C++】小知识点
  • 【MySQL】第11节|MySQL 8.0 主从复制原理分析与实战
  • 线下陪玩app小程序 陪玩同城搭子系统开发;