一文搞懂开放封闭原则

开放封闭原则(Open-Closed Principle, OCP)是面向对象设计(Object-Oriented Design)的基本原则之一,最早由 Bertrand Meyer 在其1988年的著作《Object-Oriented Software Construction》中提出。该原则的主要内容如下:

开放封闭原则定义

开放封闭原则表明:

软件实体(如类、模块、函数等)应该对扩展开放,对修改关闭。这意味着当需求发生变化时,我们应该能够在不修改原有代码的基础上,通过新增代码的方式来满足新的需求。

主要要点

  1. 扩展性: 软件实体应该容易扩展,以便在不触及原有结构和功能的基础上增加新的功能。设计时应预见到未来可能的变化,并创建抽象层和扩展点,使新功能可以通过扩展这些点来实现。
  2. 稳定性: 已经部署并经过验证的软件实体(尤其是稳定的底层组件和基础架构)应该尽可能避免修改。频繁修改既有的代码不仅增加了出错的风险,还可能引发连锁反应,影响到依赖于该代码的其他部分。

实现方式

  • 抽象类与接口:定义抽象类或接口来描述共同行为,然后通过创建新的派生类或实现接口来增加功能,而不是修改原有类。
  • 策略模式:定义一组算法族,分别封装在不同的类中,使得它们之间可以互相替换。策略模式允许算法独立于使用它的客户代码变化。
  • 装饰者模式:动态地给对象添加职责,提供了比继承更为灵活的替代方案,允许在运行时改变对象的行为。
  • 工厂方法模式:创建对象的接口,但让子类决定实例化哪一个类。这样,系统就能在不修改自身的情况下扩展产品类系列。

示例说明

下面我将以JavaScript为例,展示如何运用开放封闭原则(OCP)设计一个简单的计费系统。假设我们开始时有一个处理普通订单的计费服务,之后又需要支持VIP用户的折扣计费。

初始版本:普通订单计费服务
javascript 复制代码
// 假设有一个基础的订单类
class Order {
  constructor(totalPrice) {
    this.totalPrice = totalPrice;
  }
}

// 普通订单计费服务
class BillingService {
  calculateOrderCost(order) {
    if (!(order instanceof Order)) {
      throw new Error('Invalid order');
    }
    
    return order.totalPrice;
  }
}

// 创建一个普通订单
const regularOrder = new Order(100);

// 创建并使用计费服务
const billing = new BillingService();
console.log(billing.calculateOrderCost(regularOrder)); // 输出:100
扩展版本:支持VIP订单
javascript 复制代码
// 新增VIP订单类,它继承自Order
class VIPOrder extends Order {
  constructor(totalPrice, vipDiscount) {
    super(totalPrice);
    this.vipDiscount = vipDiscount;
  }

  applyVipDiscount() {
    return this.totalPrice * (1 - this.vipDiscount);
  }
}

// 创建一个VIP订单计费策略类,遵循策略模式
class VipBillingStrategy {
  calculateOrderCost(order) {
    if (order instanceof VIPOrder) {
      return order.applyVipDiscount();
    } else {
      throw new Error('This is not a VIP order');
    }
  }
}

// 修改BillingService,使其支持不同的计费策略
class BillingService {
  constructor(strategy = new DefaultBillingStrategy()) {
    this.strategy = strategy;
  }

  setStrategy(strategy) {
    this.strategy = strategy;
  }

  calculateOrderCost(order) {
    return this.strategy.calculateOrderCost(order);
  }
}

// 创建一个默认计费策略,用于处理普通订单
class DefaultBillingStrategy {
  calculateOrderCost(order) {
    if (order instanceof Order) {
      return order.totalPrice;
    } else {
      throw new Error('Invalid order');
    }
  }
}

// 使用VIP订单并切换计费策略
const vipOrder = new VIPOrder(200, 0.2); // 假设VIP有20%的折扣

const billing = new BillingService(new DefaultBillingStrategy());
billing.setStrategy(new VipBillingStrategy());

console.log(billing.calculateOrderCost(vipOrder)); // 输出:160

// 对于普通订单,仍然可以使用同样的计费服务
console.log(billing.calculateOrderCost(regularOrder)); // 输出:100

在这个例子中,我们遵循开放封闭原则:

  • 对扩展开放 :我们通过创建VIPOrder类以及VipBillingStrategy类,对VIP订单的处理进行了扩展,没有修改原有的Order类或BillingService的基础功能。
  • 对修改封闭 :原有的Order类和最初版本的BillingService在面对新的需求时没有被直接修改,而是通过添加新的类和策略来实现功能扩展。

为何重要

遵循开放封闭原则有助于:

  • 提高复用性:已经测试过的、稳定的代码更容易重复利用。
  • 降低耦合度:减少各个模块之间的相互依赖,使得系统更具灵活性。
  • 维护性提升:变更时只需关注新增代码,大大降低了潜在的bug引入风险。
  • 遵循开闭原则是实现软件系统持续演进的关键手段,它使得系统能够应对不断变化的需求,同时保持系统整体的稳定和可控性。
相关推荐
漂流瓶jz8 小时前
Webpack如何实现万物皆可import?loader的使用/配置/手写实践
前端·javascript·webpack
ZC跨境爬虫8 小时前
跟着 MDN 学CSS day_41:显式轨道、隐式网格与区域命名放置
前端·javascript·css·ui·交互
修己xj9 小时前
告别手动存图!这款叫 Fatkun 的浏览器插件,简直是素材收集神器
前端
袋鼠云数栈10 小时前
从前端到基础设施,ACOS 如何打通企业全链路可观测
运维·前端·人工智能·数据治理·数据智能
AskHarries10 小时前
系统提示词、开发者指令和用户输入的优先级
java·前端·数据库
Moment10 小时前
长上下文会最终杀死 Rag 吗?
前端·javascript·后端
qcx2310 小时前
【系统学AI】25 论文导读 ①:两篇改变 AI 的开山之作——Attention Is All You Need & ReAct
前端·人工智能·react.js·transformer
kyriewen11 小时前
大文件上传最全指南:分片、断点续传、秒传,一篇就够了
前端·javascript·面试
我叫黑大帅12 小时前
解决聊天页内部滚轮改为页面滚动问题
javascript·后端·面试
c++之路12 小时前
C++ 设计模式全总结
java·c++·设计模式