青雲的博客

Article

开闭原则:像太极拳那样加招式,不改拳理

· 8 分钟阅读

张三丰的太极拳以柔克刚,圆转如意,对扩展开放,对修改关闭。开闭原则就如同太极拳的理念,在面对新的需求时,我们不是去修改已有的代码,而是通过扩展现有代码来实现新的功能。就像太极拳在应对不同的对手时,通过调整招式的变化来克敌制胜,而不是改变拳法的根本原理。这样可以保证代码的稳定性和可维护性,避免因为频繁修改代码而引入新的错误。 影视剧照:一名古装男子在列阵的军队前摆出武术姿势

对扩展开放,对修改封闭

开闭原则强调:软件实体(如类、模块和函数)应该对扩展开放,对修改封闭。这意味着我们应当能够添加新的功能,而不改动现有的代码。

示例:形状的扩展

把形状抽象出来

// 抽象类:形状
abstract class Shape {
  abstract area(): number;
}

// 具体类:圆形
class Circle extends Shape {
  private radius: number;

  constructor(radius: number) {
    super();
    this.radius = radius;
  }

  area(): number {
    return Math.PI * this.radius * this.radius;
  }
}

// 具体类:矩形
class Rectangle extends Shape {
  private width: number;
  private height: number;

  constructor(width: number, height: number) {
    super();
    this.width = width;
    this.height = height;
  }

  area(): number {
    return this.width * this.height;
  }
}

扩展新的形状类

当我们需要增加新的形状,例如三角形时,可以通过扩展现有代码,而无需修改 Shape 类及其现有的子类。

// 具体类:三角形
class Triangle extends Shape {
  private base: number;
  private height: number;

  constructor(base: number, height: number) {
    super();
    this.base = base;
    this.height = height;
  }

  area(): number {
    return 0.5 * this.base * this.height;
  }
}

加一种新形状,不改旧代码

const shapes: Shape[] = [new Circle(10), new Rectangle(5, 6), new Triangle(4, 3)];
shapes.forEach(shape => console.log(shape.area()));
// 输出:314.1592653589793, 30, 6

这种设计方式使得系统具有很好的扩展性,我们可以随时添加新的形状类型,而不会影响到已有的形状类或系统的其他部分。

类图

开闭原则结构图:Shape、Circle、Rectangle、Triangle 4 个角色,以及它们之间的继承关系

体现开闭原则的设计模式

  1. 策略模式(Strategy Pattern):策略模式通过将算法封装到独立的策略类中,实现了对扩展开放、对修改封闭的要求。客户端可以在运行时选择具体的策略,而不需要修改已有代码。
  2. 装饰器模式(Decorator Pattern):装饰器模式通过将附加功能封装到装饰器对象中,使得可以在不修改具体组件代码的情况下,动态扩展对象的功能。
  3. 观察者模式(Observer Pattern):观察者模式通过将通知逻辑封装到观察者类中,可以在不修改被观察对象的情况下,动态添加新的观察者,扩展系统功能。
  4. 命令模式(Command Pattern):命令模式通过将请求封装到独立的命令类中,使得可以通过扩展新的命令类,增加新的请求处理功能,无需修改已有的代码。
  5. 工厂模式(Factory Pattern):工厂模式通过使用工厂方法创建对象,使得可以通过扩展工厂类或方法来创建新类型的对象,而不需要修改客户端代码。

需求变化时,改哪一端

组件渲染

在组件渲染场景中,可以通过扩展新的渲染策略类,实现不同的渲染方案,例如根据设备或浏览器环境选择不同的渲染策略。

class Renderer {
  render(strategy: RenderStrategy, element: HTMLElement): void {
    strategy.render(element)
  }
}

// 抽象策略类
interface RenderStrategy {
  render(element: HTMLElement): void
}

// 具体策略类:现代渲染
class ModernRender implements RenderStrategy {
  render(element: HTMLElement): void {
    element.innerHTML = "<div class='modern'>Modern Rendering</div>"
  }
}

// 具体策略类:兼容渲染
class LegacyRender implements RenderStrategy {
  render(element: HTMLElement): void {
    element.innerHTML = "<div class='legacy'>Legacy Rendering</div>"
  }
}

// 使用示例
const element = document.getElementById('myElement')
const renderer = new Renderer()
renderer.render(new ModernRender(), element) // 应用现代渲染
renderer.render(new LegacyRender(), element) // 应用兼容渲染

功能模块插拔

在功能模块插拔场景中,可以通过扩展和替换功能模块,实现动态切换功能,如换肤功能、多种排序算法等。

class Sorter {
  sort(strategy: SortStrategy, data: number[]): number[] {
    return strategy.sort(data)
  }
}

// 抽象策略类
interface SortStrategy {
  sort(data: number[]): number[]
}

// 具体策略类:快速排序
class QuickSort implements SortStrategy {
  sort(data: number[]): number[] {
    if (data.length <= 1) return data
    const pivot = data[Math.floor(data.length / 2)]
    const left = data.filter((x) => x < pivot)
    const middle = data.filter((x) => x === pivot)
    const right = data.filter((x) => x > pivot)
    return [...this.sort(left), ...middle, ...this.sort(right)]
  }
}

// 具体策略类:冒泡排序
class BubbleSort implements SortStrategy {
  sort(data: number[]): number[] {
    const n = data.length
    for (let i = 0; i < n - 1; i++) {
      for (let j = 0; j < n - 1 - i; j++) {
        if (data[j] > data[j + 1]) {
          ;[data[j], data[j + 1]] = [data[j + 1], data[j]]
        }
      }
    }
    return data
  }
}

// 使用示例
const sorter = new Sorter()
const data = [5, 3, 8, 2, 1]

console.log(sorter.sort(new QuickSort(), data)) // 使用快速排序
console.log(sorter.sort(new BubbleSort(), data)) // 使用冒泡排序

Validator.js 与 Day.js

1. Validator.js

Validator.js 是一个字符串验证库,通过策略模式实现了多种验证算法,如邮箱验证、手机号验证、URL 验证等,体现了开闭原则。

import validator from 'validator'

// 验证示例
console.log(validator.isEmail('[email protected]')) // true
console.log(validator.isMobilePhone('1234567890', 'en-US')) // true
console.log(validator.isURL('https://example.com')) // true

2. Day.js

Day.js 是一个轻量级的日期处理库,通过策略模式实现了各种日期格式化和操作算法,可以根据需求灵活使用不同的日期操作,体现了开闭原则。

import dayjs from 'dayjs'

// 示例
console.log(dayjs().format('YYYY-MM-DD')) // 输出当前日期
console.log(dayjs('2022-01-01').add(1, 'month').format('YYYY-MM-DD')) // 2022-02-01
console.log(dayjs('2022-01-01').isBefore('2022-12-31')) // true

六大原则总览与其余各篇:总览单一职责里氏替换依赖倒置接口隔离迪米特法则

Keep Reading

相关文章

评论